diff --git a/deps/5249-pjlib.diff b/deps/5249-pjlib.diff deleted file mode 100644 index d62e0ba1..00000000 --- a/deps/5249-pjlib.diff +++ /dev/null @@ -1,452 +0,0 @@ -Index: pjlib/src/pj/os_core_unix.c -=================================================================== ---- pjlib/src/pj/os_core_unix.c (revision 5249) -+++ pjlib/src/pj/os_core_unix.c (working copy) -@@ -37,6 +37,11 @@ - - #if defined(PJ_HAS_SEMAPHORE_H) && PJ_HAS_SEMAPHORE_H != 0 - # include -+# if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 -+# include -+# include -+# include -+# endif - #endif - - #include // getpid() -@@ -107,7 +112,11 @@ - #if defined(PJ_HAS_SEMAPHORE) && PJ_HAS_SEMAPHORE != 0 - struct pj_sem_t - { -+#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 -+ semaphore_t *sem; -+#else - sem_t *sem; -+#endif - char obj_name[PJ_MAX_OBJ_NAME]; - }; - #endif /* PJ_HAS_SEMAPHORE */ -@@ -1553,35 +1562,16 @@ - PJ_ASSERT_RETURN(sem, PJ_ENOMEM); - - #if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 -- /* MacOS X doesn't support anonymous semaphore */ - { -- char sem_name[PJ_GUID_MAX_LENGTH+1]; -- pj_str_t nam; -- -- /* We should use SEM_NAME_LEN, but this doesn't seem to be -- * declared anywhere? The value here is just from trial and error -- * to get the longest name supported. -- */ --# define MAX_SEM_NAME_LEN 23 -- -- /* Create a unique name for the semaphore. */ -- if (PJ_GUID_STRING_LENGTH <= MAX_SEM_NAME_LEN) { -- nam.ptr = sem_name; -- pj_generate_unique_string(&nam); -- sem_name[nam.slen] = '\0'; -- } else { -- pj_create_random_string(sem_name, MAX_SEM_NAME_LEN); -- sem_name[MAX_SEM_NAME_LEN] = '\0'; -- } -- -- /* Create semaphore */ -- sem->sem = sem_open(sem_name, O_CREAT|O_EXCL, S_IRUSR|S_IWUSR, -- initial); -- if (sem->sem == SEM_FAILED) -- return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); -- -- /* And immediately release the name as we don't need it */ -- sem_unlink(sem_name); -+ kern_return_t err; -+ sem->sem = PJ_POOL_ALLOC_T(pool, semaphore_t); -+ err = semaphore_create(mach_task_self(), sem->sem, SYNC_POLICY_FIFO, initial); -+ if (err != KERN_SUCCESS) { -+ if (err == KERN_RESOURCE_SHORTAGE) -+ return PJ_RETURN_OS_ERROR(ENOMEM); -+ else -+ return PJ_RETURN_OS_ERROR(EINVAL); -+ } - } - #else - sem->sem = PJ_POOL_ALLOC_T(pool, sem_t); -@@ -1617,6 +1607,7 @@ - { - #if PJ_HAS_THREADS - int result; -+ int error; - - PJ_CHECK_STACK(); - PJ_ASSERT_RETURN(sem, PJ_EINVAL); -@@ -1624,6 +1615,20 @@ - PJ_LOG(6, (sem->obj_name, "Semaphore: thread %s is waiting", - pj_thread_this()->obj_name)); - -+#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 -+ { -+ do -+ result = semaphore_wait(*(sem->sem)); -+ while (result == KERN_ABORTED); -+ -+ if (result == KERN_SUCCESS) { -+ result = error = 0; -+ } else { -+ result = -1; -+ error = EINVAL; -+ } -+ } -+#else - result = sem_wait( sem->sem ); - - if (result == 0) { -@@ -1632,12 +1637,14 @@ - } else { - PJ_LOG(6, (sem->obj_name, "Semaphore: thread %s FAILED to acquire", - pj_thread_this()->obj_name)); -+ error = pj_get_native_os_error(); - } -+#endif - - if (result == 0) - return PJ_SUCCESS; - else -- return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); -+ return PJ_RETURN_OS_ERROR(error); - #else - pj_assert( sem == (pj_sem_t*) 1 ); - return PJ_SUCCESS; -@@ -1651,20 +1658,45 @@ - { - #if PJ_HAS_THREADS - int result; -+ int error; - - PJ_CHECK_STACK(); - PJ_ASSERT_RETURN(sem, PJ_EINVAL); - -+#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 -+ { -+ mach_timespec_t interval; -+ kern_return_t err; -+ -+ interval.tv_sec = 0; -+ interval.tv_nsec = 0; -+ -+ err = semaphore_timedwait(*(sem->sem), interval); -+ if (err == KERN_SUCCESS) { -+ result = error = 0; -+ } else if (err == KERN_OPERATION_TIMED_OUT) { -+ result = -1; -+ error = EAGAIN; -+ } else { -+ result = -1; -+ error = EINVAL; -+ } -+ } -+#else - result = sem_trywait( sem->sem ); - - if (result == 0) { - PJ_LOG(6, (sem->obj_name, "Semaphore acquired by thread %s", - pj_thread_this()->obj_name)); -+ } else { -+ error = pj_get_native_os_error(); - } -+#endif -+ - if (result == 0) - return PJ_SUCCESS; - else -- return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); -+ return PJ_RETURN_OS_ERROR(error); - #else - pj_assert( sem == (pj_sem_t*)1 ); - return PJ_SUCCESS; -@@ -1678,14 +1710,30 @@ - { - #if PJ_HAS_THREADS - int result; -+ int error; - PJ_LOG(6, (sem->obj_name, "Semaphore released by thread %s", - pj_thread_this()->obj_name)); -+#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 -+ { -+ kern_return_t err; -+ err = semaphore_signal(*(sem->sem)); -+ if (err == KERN_SUCCESS) { -+ result = error = 0; -+ } else { -+ result = -1; -+ error = EINVAL; -+ } -+ } -+#else - result = sem_post( sem->sem ); -+ if (result != 0) -+ error = pj_get_native_os_error(); -+#endif - - if (result == 0) - return PJ_SUCCESS; - else -- return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); -+ return PJ_RETURN_OS_ERROR(error); - #else - pj_assert( sem == (pj_sem_t*) 1); - return PJ_SUCCESS; -@@ -1699,6 +1747,7 @@ - { - #if PJ_HAS_THREADS - int result; -+ int error; - - PJ_CHECK_STACK(); - PJ_ASSERT_RETURN(sem, PJ_EINVAL); -@@ -1706,15 +1755,26 @@ - PJ_LOG(6, (sem->obj_name, "Semaphore destroyed by thread %s", - pj_thread_this()->obj_name)); - #if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 -- result = sem_close( sem->sem ); -+ { -+ kern_return_t err; -+ err = semaphore_destroy(mach_task_self(), *(sem->sem)); -+ if (err == KERN_SUCCESS) { -+ result = error = -1; -+ } else { -+ result = -1; -+ error = EINVAL; -+ } -+ } - #else - result = sem_destroy( sem->sem ); -+ if (result != 0) -+ error = pj_get_native_os_error(); - #endif - - if (result == 0) - return PJ_SUCCESS; - else -- return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); -+ return PJ_RETURN_OS_ERROR(error); - #else - pj_assert( sem == (pj_sem_t*) 1 ); - return PJ_SUCCESS; -Index: pjlib/src/pj/ssl_sock_ossl.c -=================================================================== ---- pjlib/src/pj/ssl_sock_ossl.c (revision 5249) -+++ pjlib/src/pj/ssl_sock_ossl.c (working copy) -@@ -43,6 +43,7 @@ - /* - * Include OpenSSL headers - */ -+#include - #include - #include - #include -@@ -49,9 +50,24 @@ - #include - - -+#if !USING_LIBRESSL && OPENSSL_VERSION_NUMBER >= 0x10100000L -+# define OPENSSL_NO_SSL2 /* seems to be removed in 1.1.0 */ -+# define M_ASN1_STRING_data(x) ASN1_STRING_get0_data(x) -+# define M_ASN1_STRING_length(x) ASN1_STRING_length(x) -+# if defined(OPENSSL_API_COMPAT) && OPENSSL_API_COMPAT >= 0x10100000L -+# define X509_get_notBefore(x) X509_get0_notBefore(x) -+# define X509_get_notAfter(x) X509_get0_notAfter(x) -+# endif -+#else -+# define SSL_CIPHER_get_id(c) (c)->id -+# define SSL_set_session(ssl, s) (ssl)->session = (s) -+#endif -+ -+ - #ifdef _MSC_VER - # pragma comment( lib, "libeay32") - # pragma comment( lib, "ssleay32") -+# pragma comment( lib, "crypt32") - #endif - - -@@ -174,6 +190,7 @@ - send_buf_t send_buf; - write_data_t send_pending; /* list of pending write to network */ - pj_lock_t *write_mutex; /* protect write BIO and send_buf */ -+ pj_lock_t *state_mutex; /* protect the socket state (sending on one thread, destroying it in another */ - - SSL_CTX *ossl_ctx; - SSL *ossl_ssl; -@@ -318,8 +335,12 @@ - pj_assert(status == PJ_SUCCESS); - - /* Init OpenSSL lib */ -+#if OPENSSL_VERSION_NUMBER < 0x10100000L - SSL_library_init(); - SSL_load_error_strings(); -+#else -+ OPENSSL_init_ssl(0, NULL); -+#endif - #if OPENSSL_VERSION_NUMBER < 0x009080ffL - /* This is now synonym of SSL_library_init() */ - OpenSSL_add_all_algorithms(); -@@ -333,6 +354,7 @@ - STACK_OF(SSL_CIPHER) *sk_cipher; - unsigned i, n; - -+#if OPENSSL_VERSION_NUMBER < 0x10100000L - meth = (SSL_METHOD*)SSLv23_server_method(); - if (!meth) - meth = (SSL_METHOD*)TLSv1_server_method(); -@@ -344,6 +366,12 @@ - if (!meth) - meth = (SSL_METHOD*)SSLv2_server_method(); - #endif -+ -+#else -+ /* Specific version methods are deprecated in 1.1.0 */ -+ meth = (SSL_METHOD*)TLS_method(); -+#endif -+ - pj_assert(meth); - - ctx=SSL_CTX_new(meth); -@@ -360,7 +388,7 @@ - const SSL_CIPHER *c; - c = sk_SSL_CIPHER_value(sk_cipher,i); - openssl_ciphers[i].id = (pj_ssl_cipher) -- (pj_uint32_t)c->id & 0x00FFFFFF; -+ (pj_uint32_t)SSL_CIPHER_get_id(c) & 0x00FFFFFF; - openssl_ciphers[i].name = SSL_CIPHER_get_name(c); - } - -@@ -525,6 +553,7 @@ - ssock->param.proto = PJ_SSL_SOCK_PROTO_SSL23; - - /* Determine SSL method to use */ -+#if OPENSSL_VERSION_NUMBER < 0x10100000L - switch (ssock->param.proto) { - case PJ_SSL_SOCK_PROTO_TLS1: - ssl_method = (SSL_METHOD*)TLSv1_method(); -@@ -540,6 +569,10 @@ - #endif - break; - } -+#else -+ /* Specific version methods are deprecated in 1.1.0 */ -+ ssl_method = (SSL_METHOD*)TLS_method(); -+#endif - - if (!ssl_method) { - ssl_method = (SSL_METHOD*)SSLv23_method(); -@@ -810,6 +843,8 @@ - /* Reset SSL socket state */ - static void reset_ssl_sock_state(pj_ssl_sock_t *ssock) - { -+ pj_lock_acquire(ssock->state_mutex); -+ - ssock->ssl_state = SSL_STATE_NULL; - - destroy_ssl(ssock); -@@ -831,6 +866,8 @@ - * For now, just clear thread error queue here. - */ - ERR_clear_error(); -+ -+ pj_lock_release(ssock->state_mutex); - } - - -@@ -864,7 +901,8 @@ - const SSL_CIPHER *c; - c = sk_SSL_CIPHER_value(sk_cipher, j); - if (ssock->param.ciphers[i] == (pj_ssl_cipher) -- ((pj_uint32_t)c->id & 0x00FFFFFF)) -+ ((pj_uint32_t)SSL_CIPHER_get_id(c) & -+ 0x00FFFFFF)) - { - const char *c_name; - -@@ -989,7 +1027,7 @@ - pj_bool_t update_needed; - char buf[512]; - pj_uint8_t serial_no[64] = {0}; /* should be >= sizeof(ci->serial_no) */ -- pj_uint8_t *q; -+ const pj_uint8_t *q; - unsigned len; - GENERAL_NAMES *names = NULL; - -@@ -999,7 +1037,7 @@ - X509_NAME_oneline(X509_get_issuer_name(x), buf, sizeof(buf)); - - /* Get serial no */ -- q = (pj_uint8_t*) M_ASN1_STRING_data(X509_get_serialNumber(x)); -+ q = (const pj_uint8_t*) M_ASN1_STRING_data(X509_get_serialNumber(x)); - len = M_ASN1_STRING_length(X509_get_serialNumber(x)); - if (len > sizeof(ci->serial_no)) - len = sizeof(ci->serial_no); -@@ -1070,8 +1108,8 @@ - type = PJ_SSL_CERT_NAME_URI; - break; - case GEN_IPADD: -- p = ASN1_STRING_data(name->d.ip); -- len = ASN1_STRING_length(name->d.ip); -+ p = (unsigned char*)M_ASN1_STRING_data(name->d.ip); -+ len = M_ASN1_STRING_length(name->d.ip); - type = PJ_SSL_CERT_NAME_IP; - break; - default: -@@ -2200,6 +2238,12 @@ - if (status != PJ_SUCCESS) - return status; - -+ /* Create socket state mutex */ -+ status = pj_lock_create_recursive_mutex(pool, pool->obj_name, -+ &ssock->state_mutex); -+ if (status != PJ_SUCCESS) -+ return status; -+ - /* Init secure socket param */ - pj_ssl_sock_param_copy(pool, &ssock->param, param); - ssock->param.read_buffer_size = ((ssock->param.read_buffer_size+7)>>3)<<3; -@@ -2231,6 +2275,7 @@ - - reset_ssl_sock_state(ssock); - pj_lock_destroy(ssock->write_mutex); -+ pj_lock_destroy(ssock->state_mutex); - - pool = ssock->pool; - ssock->pool = NULL; -@@ -2288,7 +2333,7 @@ - - /* Current cipher */ - cipher = SSL_get_current_cipher(ssock->ossl_ssl); -- info->cipher = (cipher->id & 0x00FFFFFF); -+ info->cipher = (SSL_CIPHER_get_id(cipher) & 0x00FFFFFF); - - /* Remote address */ - pj_sockaddr_cp(&info->remote_addr, &ssock->rem_addr); -@@ -2561,9 +2606,13 @@ - - PJ_ASSERT_RETURN(ssock && data && size && (*size>0), PJ_EINVAL); - -- if (ssock->ssl_state != SSL_STATE_ESTABLISHED) -- return PJ_EINVALIDOP; -+ pj_lock_acquire(ssock->state_mutex); - -+ if (ssock->ssl_state != SSL_STATE_ESTABLISHED) { -+ status = PJ_EINVALIDOP; -+ goto on_return; -+ } -+ - // Ticket #1573: Don't hold mutex while calling PJLIB socket send(). - //pj_lock_acquire(ssock->write_mutex); - -@@ -2588,6 +2637,7 @@ - - on_return: - //pj_lock_release(ssock->write_mutex); -+ pj_lock_release(ssock->state_mutex); - return status; - } - diff --git a/deps/5249-pjmedia.diff b/deps/5249-pjmedia.diff deleted file mode 100644 index 64d45efa..00000000 --- a/deps/5249-pjmedia.diff +++ /dev/null @@ -1,6667 +0,0 @@ -Index: pjmedia/build/Makefile -=================================================================== ---- pjmedia/build/Makefile (revision 5249) -+++ pjmedia/build/Makefile (working copy) -@@ -3,11 +3,12 @@ - - THIRD_PARTY:=$(PJDIR)/third_party - -+SRTP_INC=$(CC_INC)$(THIRD_PARTY)/build/srtp \ -+ $(CC_INC)$(THIRD_PARTY)/srtp/crypto/include \ -+ $(CC_INC)$(THIRD_PARTY)/srtp/include -+ - include $(PJDIR)/build/common.mak - --export LIBDIR := ../lib --export BINDIR := ../bin -- - RULES_MAK := $(PJDIR)/build/rules.mak - - PJLIB_LIB:=$(PJDIR)/pjlib/lib/libpj-$(TARGET_NAME)$(LIBEXT) -@@ -14,27 +15,13 @@ - PJLIB_UTIL_LIB:=$(PJDIR)/pjlib-util/lib/libpjlib-util-$(TARGET_NAME)$(LIBEXT) - PJNATH_LIB:=$(PJDIR)/pjnath/lib/libpjnath-$(TARGET_NAME)$(LIBEXT) - --export PJMEDIA_LIB:=libpjmedia-$(TARGET_NAME)$(LIBEXT) --export PJMEDIA_CODEC_LIB:=libpjmedia-codec-$(TARGET_NAME)$(LIBEXT) --export PJSDP_LIB:=libpjsdp-$(TARGET_NAME)$(LIBEXT) --export PJMEDIA_AUDIODEV_LIB:=libpjmedia-audiodev-$(TARGET_NAME)$(LIBEXT) --export PJMEDIA_VIDEODEV_LIB:=libpjmedia-videodev-$(TARGET_NAME)$(LIBEXT) -+export PJMEDIA_LIB:=../lib/libpjmedia-$(TARGET_NAME)$(LIBEXT) -+export PJMEDIA_CODEC_LIB:=../lib/libpjmedia-codec-$(TARGET_NAME)$(LIBEXT) -+export PJSDP_LIB:=../lib/libpjsdp-$(TARGET_NAME)$(LIBEXT) -+export PJMEDIA_AUDIODEV_LIB:=../lib/libpjmedia-audiodev-$(TARGET_NAME)$(LIBEXT) -+export PJMEDIA_VIDEODEV_LIB:=../lib/libpjmedia-videodev-$(TARGET_NAME)$(LIBEXT) - --ifeq ($(PJ_SHARED_LIBRARIES),) --else --export PJMEDIA_SONAME:=libpjmedia.$(SHLIB_SUFFIX) --export PJMEDIA_SHLIB:=$(PJMEDIA_SONAME).$(PJ_VERSION_MAJOR) --export PJMEDIA_CODEC_SONAME:=libpjmedia-codec.$(SHLIB_SUFFIX) --export PJMEDIA_CODEC_SHLIB:=$(PJMEDIA_CODEC_SONAME).$(PJ_VERSION_MAJOR) --export PJSDP_SONAME:=libpjsdp.$(SHLIB_SUFFIX) --export PJSDP_SHLIB:=$(PJSDP_SONAME).$(PJ_VERSION_MAJOR) --export PJMEDIA_AUDIODEV_SONAME:=libpjmedia-audiodev.$(SHLIB_SUFFIX) --export PJMEDIA_AUDIODEV_SHLIB:=$(PJMEDIA_AUDIODEV_SONAME).$(PJ_VERSION_MAJOR) --export PJMEDIA_VIDEODEV_SONAME:=libpjmedia-videodev.$(SHLIB_SUFFIX) --export PJMEDIA_VIDEODEV_SHLIB:=$(PJMEDIA_VIDEODEV_SONAME).$(PJ_VERSION_MAJOR) --endif - -- - ############################################################################### - # Gather all flags. - # -@@ -44,14 +31,22 @@ - $(CC_INC)../../pjlib-util/include \ - $(CC_INC)../../pjmedia/include \ - $(CC_INC)../../pjnath/include \ -- $(CC_INC)../.. -+ $(CC_INC)../.. \ -+ $(SRTP_INC) - export _CXXFLAGS:= $(_CFLAGS) $(CC_CXXFLAGS) $(OS_CXXFLAGS) $(M_CXXFLAGS) \ - $(HOST_CXXFLAGS) $(CXXFLAGS) -- --export _LDFLAGS := $(APP_THIRD_PARTY_LIBS) \ -+export _LDFLAGS := $(subst /,$(HOST_PSEP),$(PJMEDIA_VIDEODEV_LIB)) \ -+ $(subst /,$(HOST_PSEP),$(PJMEDIA_CODEC_LIB)) \ -+ $(subst /,$(HOST_PSEP),$(PJMEDIA_LIB)) \ -+ $(subst /,$(HOST_PSEP),$(PJMEDIA_AUDIODEV_LIB)) \ -+ $(subst /,$(HOST_PSEP),$(PJLIB_LIB)) \ -+ $(subst /,$(HOST_PSEP),$(PJLIB_UTIL_LIB)) \ -+ $(subst /,$(HOST_PSEP),$(PJNATH_LIB)) \ -+ -L$(PJDIR)/third_party/lib \ -+ $(APP_THIRD_PARTY_LIBS) \ - $(APP_THIRD_PARTY_EXT) \ - $(CC_LDFLAGS) $(OS_LDFLAGS) $(M_LDFLAGS) $(HOST_LDFLAGS) \ -- $(APP_LDFLAGS) $(LDFLAGS) -+ $(LDFLAGS) - - ############################################################################### - # Defines for building PJMEDIA library -@@ -62,16 +57,16 @@ - bidirectional.o clock_thread.o codec.o conference.o \ - conf_switch.o converter.o converter_libswscale.o converter_libyuv.o \ - delaybuf.o echo_common.o \ -- echo_port.o echo_suppress.o echo_webrtc.o endpoint.o errno.o \ -+ echo_port.o echo_suppress.o endpoint.o errno.o \ - event.o format.o ffmpeg_util.o \ - g711.o jbuf.o master_port.o mem_capture.o mem_player.o \ -- null_port.o plc_common.o port.o splitcomb.o \ -- resample_resample.o resample_libsamplerate.o resample_speex.o \ -+ mixer_port.o null_port.o plc_common.o port.o splitcomb.o \ -+ resample_resample.o resample_libsamplerate.o \ - resample_port.o rtcp.o rtcp_xr.o rtp.o \ - sdp.o sdp_cmp.o sdp_neg.o session.o silencedet.o \ - sound_legacy.o sound_port.o stereo_port.o stream_common.o \ - stream.o stream_info.o tonegen.o transport_adapter_sample.o \ -- transport_ice.o transport_loop.o transport_srtp.o transport_udp.o \ -+ transport_ice.o transport_loop.o transport_srtp.o transport_zrtp.o transport_udp.o \ - types.o vid_codec.o vid_codec_util.o \ - vid_port.o vid_stream.o vid_stream_info.o vid_tee.o \ - wav_player.o wav_playlist.o wav_writer.o wave.o \ -@@ -78,13 +73,6 @@ - wsola.o - - export PJMEDIA_CFLAGS += $(_CFLAGS) --export PJMEDIA_CXXFLAGS += $(_CXXFLAGS) --export PJMEDIA_LDFLAGS += $(PJMEDIA_VIDEODEV_LDLIB) \ -- $(PJMEDIA_AUDIODEV_LDLIB) \ -- $(PJLIB_LDLIB) \ -- $(PJLIB_UTIL_LDLIB) \ -- $(PJNATH_LDLIB) \ -- $(_LDFLAGS) - - - ############################################################################### -@@ -91,14 +79,8 @@ - # Defines for building PJMEDIA-AUDIODEV library - # - export PJMEDIA_AUDIODEV_SRCDIR = ../src/pjmedia-audiodev --export PJMEDIA_AUDIODEV_OBJS += audiodev.o audiotest.o errno.o \ -- legacy_dev.o null_dev.o pa_dev.o wmme_dev.o \ -- alsa_dev.o bb10_dev.o bdimad_dev.o \ -- android_jni_dev.o opensl_dev.o -+export PJMEDIA_AUDIODEV_OBJS += audiodev.o errno.o null_dev.o wmme_dev.o alsa_dev.o - export PJMEDIA_AUDIODEV_CFLAGS += $(_CFLAGS) --export PJMEDIA_AUDIODEV_CXXFLAGS += $(_CXXFLAGS) --export PJMEDIA_AUDIODEV_LDFLAGS += $(PJLIB_LDLIB) \ -- $(_LDFLAGS) - - - ############################################################################### -@@ -105,13 +87,9 @@ - # Defines for building PJMEDIA-VIDEODEV library - # - export PJMEDIA_VIDEODEV_SRCDIR = ../src/pjmedia-videodev --export PJMEDIA_VIDEODEV_OBJS += errno.o videodev.o avi_dev.o ffmpeg_dev.o \ -- colorbar_dev.o v4l2_dev.o opengl_dev.o \ -- util.o -+export PJMEDIA_VIDEODEV_OBJS += errno.o videodev.o avi_dev.o colorbar_dev.o v4l2_dev.o fb_dev.o null_dev.o util.o - export PJMEDIA_VIDEODEV_CFLAGS += $(_CFLAGS) - export PJMEDIA_VIDEODEV_CXXFLAGS += $(_CXXFLAGS) --export PJMEDIA_VIDEODEV_LDFLAGS += $(PJLIB_LDLIB) \ -- $(_LDFLAGS) - - - ############################################################################### -@@ -124,11 +102,6 @@ - export PJSDP_OBJS += $(OS_OBJS) $(M_OBJS) $(CC_OBJS) $(HOST_OBJS) \ - errno.o sdp.o sdp_cmp.o sdp_neg.o - export PJSDP_CFLAGS += $(_CFLAGS) --export PJSDP_CXXFLAGS += $(_CXXFLAGS) --export PJSDP_LDFLAGS += $(PJMEDIA_LDLIB) \ -- $(PJLIB_LDLIB) \ -- $(PJLIB_UTIL_LDLIB) \ -- $(_LDFLAGS) - - - ############################################################################### -@@ -136,18 +109,16 @@ - # - export PJMEDIA_CODEC_SRCDIR = ../src/pjmedia-codec - export PJMEDIA_CODEC_OBJS += audio_codecs.o ffmpeg_vid_codecs.o openh264.o \ -- h263_packetizer.o h264_packetizer.o \ -+ h263_packetizer.o h264_packetizer.o vpx.o \ - $(OS_OBJS) $(M_OBJS) $(CC_OBJS) $(HOST_OBJS) \ -- ipp_codecs.o silk.o opus.o $(CODEC_OBJS) \ -- g7221_sdp_match.o amr_sdp_match.o -+ $(CODEC_OBJS) \ -+ g7221_sdp_match.o - export PJMEDIA_CODEC_CFLAGS += $(_CFLAGS) $(GSM_CFLAGS) $(SPEEX_CFLAGS) \ - $(ILBC_CFLAGS) $(IPP_CFLAGS) $(G7221_CFLAGS) - export PJMEDIA_CODEC_CXXFLAGS += $(_CXXFLAGS) $(GSM_CFLAGS) $(SPEEX_CFLAGS) \ - $(ILBC_CFLAGS) $(IPP_CFLAGS) $(G7221_CFLAGS) --export PJMEDIA_CODEC_LDFLAGS += $(PJMEDIA_LDLIB) \ -- $(PJLIB_LDLIB) \ -- $(_LDFLAGS) - -+ - ############################################################################### - # Defines for building test application - # -@@ -157,16 +128,8 @@ - rtp_test.o test.o - export PJMEDIA_TEST_OBJS += sdp_neg_test.o - export PJMEDIA_TEST_CFLAGS += $(_CFLAGS) --export PJMEDIA_TEST_CXXFLAGS += $(_CXXFLAGS) --export PJMEDIA_TEST_LDFLAGS += $(PJMEDIA_CODEC_LDLIB) \ -- $(PJMEDIA_VIDEODEV_LDLIB) \ -- $(PJMEDIA_LDLIB) \ -- $(PJMEDIA_AUDIODEV_LDLIB) \ -- $(PJLIB_LDLIB) \ -- $(PJLIB_UTIL_LDLIB) \ -- $(PJNATH_LDLIB) \ -- $(_LDFLAGS) --export PJMEDIA_TEST_EXE:=pjmedia-test-$(TARGET_NAME)$(HOST_EXE) -+export PJMEDIA_TEST_LDFLAGS += $(_LDFLAGS) -+export PJMEDIA_TEST_EXE:=../bin/pjmedia-test-$(TARGET_NAME)$(HOST_EXE) - - - export CC_OUT CC AR RANLIB HOST_MV HOST_RM HOST_RMDIR HOST_MKDIR OBJEXT LD LDOUT -@@ -175,17 +138,10 @@ - # - # $(TARGET) is defined in os-$(OS_NAME).mak file in current directory. - # --TARGETS := $(PJMEDIA_LIB) $(PJMEDIA_SONAME) \ -- $(PJMEDIA_CODEC_LIB) $(PJMEDIA_CODEC_SONAME)\ -- $(PJMEDIA_VIDEODEV_LIB) $(PJMEDIA_VIDEODEV_SONAME) \ -- $(PJMEDIA_AUDIODEV_LIB) $(PJMEDIA_AUDIODEV_SONAME) \ -- $(PJSDP_LIB) $(PJSDP_SONAME) --TARGETS_EXE := $(PJMEDIA_TEST_EXE) -+TARGETS := pjmedia pjmedia-videodev pjmedia-audiodev pjmedia-codec pjsdp - --all: $(TARGETS) $(TARGETS_EXE) -+all: $(TARGETS) - --lib: $(TARGETS) -- - doc: - cd .. && rm -rf docs/$(PJ_VERSION) && doxygen docs/doxygen.cfg - @if [ -n "$(WWWDIR)" ] && ! [ -d "$(WWWDIR)/docs/$(PJ_VERSION)/pjmedia/docs/html" ] ; then \ -@@ -200,61 +156,41 @@ - dep: depend - distclean: realclean - --.PHONY: all dep depend clean realclean distclean --.PHONY: $(TARGETS) --.PHONY: $(PJMEDIA_LIB) $(PJMEDIA_SONAME) --.PHONY: $(PJMEDIA_CODEC_LIB) $(PJMEDIA_CODEC_SONAME) --.PHONY: $(PJMEDIA_VIDEODEV_LIB) $(PJMEDIA_VIDEODEV_SONAME) --.PHONY: $(PJMEDIA_AUDIODEV_LIB) $(PJMEDIA_AUDIODEV_SONAME) --.PHONY: $(PJSDP_LIB) $(PJSDP_SONAME) --.PHONY: $(PJMEDIA_TEST_EXE) -+.PHONY: dep depend pjmedia pjmedia-codec pjmedia-videodev pjmedia-audiodev pjmedia-test clean realclean distclean - --pjmedia: $(PJMEDIA_LIB) --$(PJMEDIA_SONAME): $(PJMEDIA_LIB) --$(PJMEDIA_LIB) $(PJMEDIA_SONAME): $(PJMEDIA_AUDIODEV_LIB) $(PJMEDIA_AUDIODEV_SONAME) $(PJMEDIA_VIDEODEV_LIB) $(PJMEDIA_VIDEODEV_SONAME) -- $(MAKE) -f $(RULES_MAK) APP=PJMEDIA app=pjmedia $(subst /,$(HOST_PSEP),$(LIBDIR)/$@) -+pjmedia: -+ $(MAKE) -f $(RULES_MAK) APP=PJMEDIA app=pjmedia $(PJMEDIA_LIB) - --pjmedia-codec: $(PJMEDIA_CODEC_LIB) --$(PJMEDIA_CODEC_SONAME): $(PJMEDIA_CODEC_LIB) --$(PJMEDIA_CODEC_LIB) $(PJMEDIA_CODEC_SONAME): $(PJMEDIA_LIB) $(PJMEDIA_SONAME) -- $(MAKE) -f $(RULES_MAK) APP=PJMEDIA_CODEC app=pjmedia-codec $(subst /,$(HOST_PSEP),$(LIBDIR)/$@) -+pjmedia-codec: -+ $(MAKE) -f $(RULES_MAK) APP=PJMEDIA_CODEC app=pjmedia-codec $(PJMEDIA_CODEC_LIB) - --pjmedia-videodev: $(PJMEDIA_VIDEODEV_LIB) --$(PJMEDIA_VIDEODEV_SONAME): $(PJMEDIA_VIDEODEV_LIB) --$(PJMEDIA_VIDEODEV_LIB) $(PJMEDIA_VIDEODEV_SONAME): -- $(MAKE) -f $(RULES_MAK) APP=PJMEDIA_VIDEODEV app=pjmedia-videodev $(subst /,$(HOST_PSEP),$(LIBDIR)/$@) -+pjmedia-videodev: -+ $(MAKE) -f $(RULES_MAK) APP=PJMEDIA_VIDEODEV app=pjmedia-videodev $(PJMEDIA_VIDEODEV_LIB) - --pjmedia-audiodev: $(PJMEDIA_AUDIODEV_LIB) --$(PJMEDIA_AUDIODEV_SONAME): $(PJMEDIA_AUDIODEV_LIB) --$(PJMEDIA_AUDIODEV_LIB) $(PJMEDIA_AUDIODEV_SONAME): -- $(MAKE) -f $(RULES_MAK) APP=PJMEDIA_AUDIODEV app=pjmedia-audiodev $(subst /,$(HOST_PSEP),$(LIBDIR)/$@) -+pjmedia-audiodev: -+ $(MAKE) -f $(RULES_MAK) APP=PJMEDIA_AUDIODEV app=pjmedia-audiodev $(PJMEDIA_AUDIODEV_LIB) - --pjsdp: $(PJSDP_LIB) --$(PJSDP_SONAME): $(PJSDP_LIB) --$(PJSDP_LIB) $(PJSDP_SONAME): $(PJMEDIA_LIB) $(PJMEDIA_SONAME) -- $(MAKE) -f $(RULES_MAK) APP=PJSDP app=pjsdp $(LIBDIR)/$@ -+pjsdp: -+ $(MAKE) -f $(RULES_MAK) APP=PJSDP app=pjsdp $(PJSDP_LIB) - --pjmedia-test: $(PJMEDIA_TEST_EXE) --$(PJMEDIA_TEST_EXE): $(PJMEDIA_LIB) $(PJMEDIA_SONAME) --$(PJMEDIA_TEST_EXE): $(PJMEDIA_AUDIODEV_LIB) $(PJMEDIA_AUDIODEV_SONAME) --$(PJMEDIA_TEST_EXE): $(PJMEDIA_VIDEODEV_LIB) $(PJMEDIA_VIDEODEV_SONAME) --$(PJMEDIA_TEST_EXE): $(PJMEDIA_CODEC_LIB) $(PJMEDIA_CODEC_SONAME) --$(PJMEDIA_TEST_EXE): -- $(MAKE) -f $(RULES_MAK) APP=PJMEDIA_TEST app=pjmedia-test $(BINDIR)/$@ -+$(PJMEDIA_LIB): pjmedia - --.PHONY: pjmedia.ko --pjmedia.ko: -+pjmedia-test: $(PJMEDIA_LIB) pjmedia -+ $(MAKE) -f $(RULES_MAK) APP=PJMEDIA_TEST app=pjmedia-test $(PJMEDIA_TEST_EXE) -+ -+.PHONY: ../lib/pjmedia.ko -+../lib/pjmedia.ko: - echo Making $@ -- $(MAKE) -f $(RULES_MAK) APP=PJMEDIA app=pjmedia $(LIBDIR)/$@ -+ $(MAKE) -f $(RULES_MAK) APP=PJMEDIA app=pjmedia $@ - --.PHONY: pjmedia-codec.ko --pjmedia-codec.ko: -+.PHONY: ../lib/pjmedia-codec.ko -+../lib/pjmedia-codec.ko: - echo Making $@ -- $(MAKE) -f $(RULES_MAK) APP=PJMEDIA_CODEC app=pjmedia-codec $(LIBDIR)/$@ -+ $(MAKE) -f $(RULES_MAK) APP=PJMEDIA_CODEC app=pjmedia-codec $@ - --.PHONY: pjmedia-test.ko --pjmedia-test.ko: -- $(MAKE) -f $(RULES_MAK) APP=PJMEDIA_TEST app=pjmedia-test $(LIBDIR)/$@ -+.PHONY: ../lib/pjmedia-test.ko -+../lib/pjmedia-test.ko: -+ $(MAKE) -f $(RULES_MAK) APP=PJMEDIA_TEST app=pjmedia-test $@ - - clean: - $(MAKE) -f $(RULES_MAK) APP=PJMEDIA app=pjmedia $@ -@@ -286,6 +222,6 @@ - $(MAKE) -f $(RULES_MAK) APP=PJMEDIA_CODEC app=pjmedia-codec $@ - $(MAKE) -f $(RULES_MAK) APP=PJMEDIA_TEST app=pjmedia-test $@ - $(MAKE) -f $(RULES_MAK) APP=PJSDP app=pjsdp $@ -- echo '$(BINDIR)/$(PJMEDIA_TEST_EXE): $(LIBDIR)/$(PJMEDIA_LIB) $(LIBDIR)/$(PJMEDIA_CODEC_LIB) $(PJNATH_LIB) $(PJLIB_UTIL_LIB) $(PJLIB_LIB)' >> .pjmedia-test-$(TARGET_NAME).depend -+ echo '$(PJMEDIA_TEST_EXE): $(PJMEDIA_LIB) $(PJMEDIA_CODEC_LIB) $(PJNATH_LIB) $(PJLIB_UTIL_LIB) $(PJLIB_LIB)' >> .pjmedia-test-$(TARGET_NAME).depend - - -Index: pjmedia/build/os-auto.mak.in -=================================================================== ---- pjmedia/build/os-auto.mak.in (revision 5249) -+++ pjmedia/build/os-auto.mak.in (working copy) -@@ -7,27 +7,29 @@ - # - android_os - AC_PJMEDIA_VIDEO = @ac_pjmedia_video@ - --# SDL flags --SDL_CFLAGS = @ac_sdl_cflags@ --SDL_LDFLAGS = @ac_sdl_ldflags@ -- - # FFMPEG dlags - FFMPEG_CFLAGS = @ac_ffmpeg_cflags@ - FFMPEG_LDFLAGS = @ac_ffmpeg_ldflags@ - -+# VPX flags -+VPX_CFLAGS = @ac_vpx_cflags@ -+VPX_LDFLAGS = @ac_vpx_ldflags@ -+ - # Video4Linux2 - V4L2_CFLAGS = @ac_v4l2_cflags@ - V4L2_LDFLAGS = @ac_v4l2_ldflags@ - --# QT --AC_PJMEDIA_VIDEO_HAS_QT = @ac_pjmedia_video_has_qt@ --QT_CFLAGS = @ac_qt_cflags@ -+# AVF -+AC_PJMEDIA_VIDEO_HAS_AVF = @ac_pjmedia_video_has_avf@ -+AVF_CFLAGS = @ac_avf_cflags@ - - # iOS - IOS_CFLAGS = @ac_ios_cflags@ - --# Android --ANDROID_CFLAGS = @ac_android_cflags@ -+# Dshow -+AC_PJMEDIA_VIDEO_HAS_DSHOW = @ac_pjmedia_video_has_dshow@ -+DSHOW_CFLAGS = @ac_dshow_cflags@ -+DSHOW_LDFLAGS = @ac_dshow_ldflags@ - - # libyuv - LIBYUV_CFLAGS = @ac_libyuv_cflags@ -@@ -37,75 +39,26 @@ - OPENH264_CFLAGS = @ac_openh264_cflags@ - OPENH264_LDFLAGS = @ac_openh264_ldflags@ - --# WebRtc --WEBRTC_CFLAGS = @ac_webrtc_cflags@ --WEBRTC_LDFLAGS = @ac_webrtc_ldflags@ -- -- - # PJMEDIA features exclusion - export CFLAGS += @ac_no_small_filter@ @ac_no_large_filter@ @ac_no_speex_aec@ \ -- $(SDL_CFLAGS) $(FFMPEG_CFLAGS) $(V4L2_CFLAGS) $(QT_CFLAGS) \ -- $(IOS_CFLAGS) $(ANDROID_CFLAGS) $(LIBYUV_CFLAGS) \ -- $(OPENH264_CFLAGS) $(WEBRTC_CFLAGS) --export LDFLAGS += $(SDL_LDFLAGS) $(FFMPEG_LDFLAGS) $(V4L2_LDFLAGS) \ -- $(LIBYUV_LDFLAGS) $(OPENH264_LDFLAGS) $(WEBRTC_LDFLAGS) -+ $(FFMPEG_CFLAGS) $(V4L2_CFLAGS) $(AVF_CFLAGS) \ -+ $(IOS_CFLAGS) $(DSHOW_CFLAGS) $(LIBYUV_CFLAGS) $(OPENH264_CFLAGS) \ -+ $(VPX_CFLAGS) -+export LDFLAGS += $(FFMPEG_LDFLAGS) $(V4L2_LDFLAGS) $(DSHOW_LDFLAGS) \ -+ $(LIBYUV_LDFLAGS) $(OPENH264_LDFLAGS) $(VPX_LDFLAGS) - --# Define the desired sound device backend --# Valid values are: --# - pa_unix: PortAudio on Unix (OSS or ALSA) --# - pa_darwinos: PortAudio on MacOSX (CoreAudio) --# - pa_old_darwinos: PortAudio on MacOSX (old CoreAudio, for OSX 10.2) --# - pa_win32: PortAudio on Win32 (WMME) --# - win32: Win32 MME (wmme_dev.c) --# - coreaudio: MaxOSX CoreAudio (coreaudio_dev.m) --# - alsa: Unix ALSA (alsa_dev.c) --# - null: Null sound device (nullsound.c) --# - external: Link with no sounddev (app will provide) --AC_PJMEDIA_SND=@ac_pjmedia_snd@ - --# For Unix, specify if ALSA should be supported --AC_PA_USE_ALSA=@ac_pa_use_alsa@ -- --# Additional PortAudio CFLAGS are in @ac_pa_cflags@ -- - # - # Codecs - # --AC_NO_G711_CODEC=@ac_no_g711_codec@ --AC_NO_L16_CODEC=@ac_no_l16_codec@ --AC_NO_GSM_CODEC=@ac_no_gsm_codec@ --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@ --AC_NO_OPENCORE_AMRWB=@ac_no_opencore_amrwb@ - - export CODEC_OBJS= - --export PJMEDIA_AUDIODEV_OBJS += @ac_pjmedia_audiodev_objs@ -+export PJMEDIA_AUDIODEV_OBJS += @ac_pjmedia_audiodev_objs@ - --ifeq ($(AC_NO_G711_CODEC),1) --export CFLAGS += -DPJMEDIA_HAS_G711_CODEC=0 --else --export CODEC_OBJS += --endif -- --ifeq ($(AC_NO_L16_CODEC),1) --export CFLAGS += -DPJMEDIA_HAS_L16_CODEC=0 --else --export CODEC_OBJS += l16.o --endif -- --ifeq ($(AC_NO_GSM_CODEC),1) --export CFLAGS += -DPJMEDIA_HAS_GSM_CODEC=0 --else - export CODEC_OBJS += gsm.o --endif - --ifeq ($(AC_NO_SPEEX_CODEC),1) --export CFLAGS += -DPJMEDIA_HAS_SPEEX_CODEC=0 --else - export CFLAGS += -I$(THIRD_PARTY)/build/speex -I$(THIRD_PARTY)/speex/include - export CODEC_OBJS += speex_codec.o - -@@ -113,19 +66,12 @@ - export PJMEDIA_OBJS += echo_speex.o - endif - --endif -+export CFLAGS += -I$(THIRD_PARTY)/webrtc/src -+export PJMEDIA_OBJS += echo_webrtc_aec.o - --ifeq ($(AC_NO_ILBC_CODEC),1) --export CFLAGS += -DPJMEDIA_HAS_ILBC_CODEC=0 --else - export CODEC_OBJS += ilbc.o --endif - --ifeq ($(AC_NO_G722_CODEC),1) --export CFLAGS += -DPJMEDIA_HAS_G722_CODEC=0 --else - export CODEC_OBJS += g722.o g722/g722_enc.o g722/g722_dec.o --endif - - ifeq ($(AC_NO_G7221_CODEC),1) - export CFLAGS += -DPJMEDIA_HAS_G7221_CODEC=0 -@@ -134,148 +80,19 @@ - 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_amr.o --endif -+export CODEC_OBJS += opus.o - --ifeq ($(AC_NO_OPENCORE_AMRWB),1) --export CFLAGS += -DPJMEDIA_HAS_OPENCORE_AMRWB_CODEC=0 --else --ifeq ($(AC_NO_OPENCORE_AMRNB),1) --export CODEC_OBJS += opencore_amr.o --endif --endif -- -- - # --# SRTP -+# Dshow video device - # --ifeq (@ac_external_srtp@,1) --# External SRTP --export CFLAGS += -DPJMEDIA_EXTERNAL_SRTP=1 --# SRTP srtp_deinit()/srtp_shutdown() API availability settings --export CFLAGS += -DPJMEDIA_SRTP_HAS_DEINIT=@ac_srtp_deinit_present@ \ -- -DPJMEDIA_SRTP_HAS_SHUTDOWN=@ac_srtp_shutdown_present@ --else --# Our SRTP in third_party --export CFLAGS += -I$(THIRD_PARTY)/build/srtp \ -- -I$(THIRD_PARTY)/srtp/crypto/include \ -- -I$(THIRD_PARTY)/srtp/include -- -+ifeq ($(AC_PJMEDIA_VIDEO_HAS_DSHOW),yes) -+export PJMEDIA_VIDEODEV_OBJS += dshow_dev.o dshow_filter.o - endif - - # --# Resample -+# AVF video device - # --AC_PJMEDIA_RESAMPLE=@ac_pjmedia_resample@ -- --ifeq ($(AC_PJMEDIA_RESAMPLE),none) --# No resample support --export CFLAGS += -DPJMEDIA_RESAMPLE_IMP=PJMEDIA_RESAMPLE_NONE -+ifeq ($(AC_PJMEDIA_VIDEO_HAS_AVF),yes) -+export PJMEDIA_VIDEODEV_OBJS += avf_dev.o - endif - --ifeq ($(AC_PJMEDIA_RESAMPLE),libresample) --export CFLAGS += -DPJMEDIA_RESAMPLE_IMP=PJMEDIA_RESAMPLE_LIBRESAMPLE --endif -- --ifeq ($(AC_PJMEDIA_RESAMPLE),libsamplerate) --export CFLAGS += -DPJMEDIA_RESAMPLE_IMP=PJMEDIA_RESAMPLE_LIBSAMPLERATE --endif -- --ifeq ($(AC_PJMEDIA_RESAMPLE),speex) --export CFLAGS += -DPJMEDIA_RESAMPLE_IMP=PJMEDIA_RESAMPLE_SPEEX --endif -- --# --# PortAudio --# --ifneq ($(findstring pa,$(AC_PJMEDIA_SND)),) --ifeq (@ac_external_pa@,1) --# External PA --export CFLAGS += -DPJMEDIA_AUDIO_DEV_HAS_PORTAUDIO=1 --else --# Our PA in third_party --export CFLAGS += -I$(THIRD_PARTY)/build/portaudio \ -- -I$(THIRD_PARTY)/portaudio/include \ -- -DPJMEDIA_AUDIO_DEV_HAS_PORTAUDIO=1 --endif --endif -- --# --# MacOSX specific --# --ifneq ($(findstring coreaudio,$(AC_PJMEDIA_SND)),) --export CFLAGS += -DPJMEDIA_AUDIO_DEV_HAS_COREAUDIO=1 \ -- -DPJMEDIA_AUDIO_DEV_HAS_PORTAUDIO=0 \ -- -DPJMEDIA_AUDIO_DEV_HAS_WMME=0 --endif -- --# --# Unix specific --# --ifneq ($(findstring alsa,$(AC_PJMEDIA_SND)),) --export CFLAGS += -DPJMEDIA_AUDIO_DEV_HAS_ALSA=1 \ -- -DPJMEDIA_AUDIO_DEV_HAS_PORTAUDIO=0 \ -- -DPJMEDIA_AUDIO_DEV_HAS_WMME=0 --endif -- --# --# Windows specific --# --ifneq ($(findstring win32,$(AC_PJMEDIA_SND)),) --export CFLAGS += -DPJMEDIA_AUDIO_DEV_HAS_WMME=1 \ -- -DPJMEDIA_AUDIO_DEV_HAS_PORTAUDIO=0 --else --export CFLAGS += -DPJMEDIA_AUDIO_DEV_HAS_WMME=0 --endif -- --# --# Null sound device --# --ifeq ($(AC_PJMEDIA_SND),null) --export CFLAGS += -DPJMEDIA_AUDIO_DEV_HAS_PORTAUDIO=0 \ -- -DPJMEDIA_AUDIO_DEV_HAS_WMME=0 --endif -- --# --# External sound device --# --ifeq ($(AC_PJMEDIA_SND),external) --export CFLAGS += -DPJMEDIA_AUDIO_DEV_HAS_PORTAUDIO=0 \ -- -DPJMEDIA_AUDIO_DEV_HAS_WMME=0 --endif -- --# --# QT video device --# --ifeq ($(AC_PJMEDIA_VIDEO_HAS_QT),yes) --export PJMEDIA_VIDEODEV_OBJS += qt_dev.o --endif -- --# --# iOS video device --# --ifeq ($(AC_PJMEDIA_VIDEO),iphone_os) --export PJMEDIA_VIDEODEV_OBJS += ios_dev.o ios_opengl_dev.o --endif -- --# --# Android video device --# --ifeq ($(AC_PJMEDIA_VIDEO),android_os) --export PJMEDIA_VIDEODEV_OBJS += android_dev.o android_opengl.o --endif -- --# --# Determine whether we should compile the obj-c version of a particular --# source code --# --ifneq (,$(filter $(AC_PJMEDIA_VIDEO),mac_os iphone_os)) --# Mac and iPhone OS specific, use obj-c --export PJMEDIA_VIDEODEV_OBJS += sdl_dev_m.o --else --# Other platforms, compile .c --export PJMEDIA_VIDEODEV_OBJS += sdl_dev.o --endif -Index: pjmedia/include/pjmedia/config.h -=================================================================== ---- pjmedia/include/pjmedia/config.h (revision 5249) -+++ pjmedia/include/pjmedia/config.h (working copy) -@@ -657,24 +657,6 @@ - - - /** -- * WebRtc Accoustic Echo Cancellation (AEC). -- * By default is disabled. -- */ --#ifndef PJMEDIA_HAS_WEBRTC_AEC --# define PJMEDIA_HAS_WEBRTC_AEC 0 --#endif -- --/** -- * Specify whether WebRtc EC should use its mobile version AEC. -- * -- * Default: 0 (no) -- */ --#ifndef PJMEDIA_WEBRTC_AEC_USE_MOBILE --# define PJMEDIA_WEBRTC_AEC_USE_MOBILE 0 --#endif -- -- --/** - * Maximum number of parameters in SDP fmtp attribute. - * - * Default: 16 -@@ -972,7 +954,6 @@ - * See: - * - G.722 : RFC 3551 4.5.2 - * - MPEG audio : RFC 3551 4.5.13 & RFC 3119 -- * - OPUS : RFC 7587 - * - * Also when this feature is enabled, some handling will be performed - * to deal with clock rate incompatibilities of some phones. -@@ -1078,10 +1059,10 @@ - /** - * Top level option to enable/disable video features. - * -- * Default: 0 (disabled) -+ * Default: 1 (enabled) - */ - #ifndef PJMEDIA_HAS_VIDEO --# define PJMEDIA_HAS_VIDEO 0 -+# define PJMEDIA_HAS_VIDEO 1 - #endif - - -@@ -1132,21 +1113,21 @@ - #endif - - /** -- * Specify if FFMPEG libavdevice is available. -+ * Specify if FFMPEG libavcore is available. - * - * Default: PJMEDIA_HAS_FFMPEG (or detected by configure) - */ --#ifndef PJMEDIA_HAS_LIBAVDEVICE --# define PJMEDIA_HAS_LIBAVDEVICE PJMEDIA_HAS_FFMPEG -+#ifndef PJMEDIA_HAS_LIBAVCORE -+# define PJMEDIA_HAS_LIBAVCORE PJMEDIA_HAS_FFMPEG - #endif - - /** -- * Specify if FFMPEG libavcore is available. -+ * Specify if libvpx is available. - * -- * Default: PJMEDIA_HAS_FFMPEG (or detected by configure) -+ * Default: 0 (or detected by configure) - */ --#ifndef PJMEDIA_HAS_LIBAVCORE --# define PJMEDIA_HAS_LIBAVCORE PJMEDIA_HAS_FFMPEG -+#ifndef PJMEDIA_HAS_LIBVPX -+# define PJMEDIA_HAS_LIBVPX 0 - #endif - - /** -@@ -1321,7 +1302,6 @@ - # endif - #endif - -- - /** - * Specify if libyuv is available. - * -Index: pjmedia/include/pjmedia/echo.h -=================================================================== ---- pjmedia/include/pjmedia/echo.h (revision 5249) -+++ pjmedia/include/pjmedia/echo.h (working copy) -@@ -66,8 +66,7 @@ - - /** - * Force to use Speex AEC as the backend echo canceller algorithm. -- * This setting is mutually exclusive with PJMEDIA_ECHO_SIMPLE and -- * PJMEDIA_ECHO_WEBRTC. -+ * This setting is mutually exclusive with PJMEDIA_ECHO_SIMPLE. - */ - PJMEDIA_ECHO_SPEEX = 1, - -@@ -75,14 +74,13 @@ - * If PJMEDIA_ECHO_SIMPLE flag is specified during echo canceller - * creation, then a simple echo suppressor will be used instead of - * an accoustic echo cancellation. This setting is mutually exclusive -- * with PJMEDIA_ECHO_SPEEX and PJMEDIA_ECHO_WEBRTC. -+ * with PJMEDIA_ECHO_SPEEX. - */ - PJMEDIA_ECHO_SIMPLE = 2, - - /** - * Force to use WebRTC AEC as the backend echo canceller algorithm. -- * This setting is mutually exclusive with PJMEDIA_ECHO_SIMPLE and -- * PJMEDIA_ECHO_SPEEX. -+ * This setting is mutually exclusive with PJMEDIA_ECHO_SIMPLE & PJMEDIA_ECHO_SPEEX. - */ - PJMEDIA_ECHO_WEBRTC = 3, - -@@ -109,46 +107,7 @@ - * If PJMEDIA_ECHO_USE_SW_ECHO flag is specified, software echo canceller - * will be used instead of device EC. - */ -- PJMEDIA_ECHO_USE_SW_ECHO = 64, -- -- /** -- * If PJMEDIA_ECHO_USE_NOISE_SUPPRESSOR flag is specified, the echo -- * canceller will also apply noise suppressor method to reduce noise. -- */ -- PJMEDIA_ECHO_USE_NOISE_SUPPRESSOR = 128, -- -- /** -- * Use default aggressiveness setting for the echo canceller algorithm. -- * This setting is mutually exclusive with the other aggressiveness -- * settings. -- */ -- PJMEDIA_ECHO_AGGRESSIVENESS_DEFAULT = 0, -- -- /** -- * Use conservative aggressiveness setting for the echo canceller -- * algorithm. This setting is mutually exclusive with the other -- * aggressiveness settings. -- */ -- PJMEDIA_ECHO_AGGRESSIVENESS_CONSERVATIVE = 0x100, -- -- /** -- * Use moderate aggressiveness setting for the echo canceller algorithm. -- * This setting is mutually exclusive with the other aggressiveness -- * settings. -- */ -- PJMEDIA_ECHO_AGGRESSIVENESS_MODERATE = 0x200, -- -- /** -- * Use aggressive aggressiveness setting for the echo canceller -- * algorithm. This setting is mutually exclusive with the other -- * aggressiveness settings. -- */ -- PJMEDIA_ECHO_AGGRESSIVENESS_AGGRESSIVE = 0x300, -- -- /** -- * For internal use. -- */ -- PJMEDIA_ECHO_AGGRESSIVENESS_MASK = 0xF00 -+ PJMEDIA_ECHO_USE_SW_ECHO = 64 - - } pjmedia_echo_flag; - -Index: pjmedia/include/pjmedia/errno.h -=================================================================== ---- pjmedia/include/pjmedia/errno.h (revision 5249) -+++ pjmedia/include/pjmedia/errno.h (working copy) -@@ -46,18 +46,6 @@ - #define PJMEDIA_ERRNO_END (PJMEDIA_ERRNO_START + PJ_ERRNO_SPACE_SIZE - 1) - - --/** -- * Mapping from PortAudio error codes to pjmedia error space. -- */ --#define PJMEDIA_PORTAUDIO_ERRNO_START (PJMEDIA_ERRNO_END-10000) --#define PJMEDIA_PORTAUDIO_ERRNO_END (PJMEDIA_PORTAUDIO_ERRNO_START + 10000 -1) --/** -- * Convert PortAudio error code to PJMEDIA error code. -- * PortAudio error code range: 0 >= err >= -10000 -- */ --#define PJMEDIA_ERRNO_FROM_PORTAUDIO(err) ((int)PJMEDIA_PORTAUDIO_ERRNO_START-err) -- -- - #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0) - - /** -Index: pjmedia/include/pjmedia/event.h -=================================================================== ---- pjmedia/include/pjmedia/event.h (revision 5249) -+++ pjmedia/include/pjmedia/event.h (working copy) -@@ -80,6 +80,11 @@ - PJMEDIA_EVENT_KEYFRAME_MISSING = PJMEDIA_FOURCC('I', 'F', 'R', 'M'), - - /** -+ * Remote video decoder asked for a keyframe. -+ */ -+ PJMEDIA_EVENT_KEYFRAME_REQUESTED = PJMEDIA_FOURCC('I', 'F', 'R', 'R'), -+ -+ /** - * Video orientation has been changed event. - */ - PJMEDIA_EVENT_ORIENT_CHANGED = PJMEDIA_FOURCC('O', 'R', 'N', 'T') -Index: pjmedia/include/pjmedia/format.h -=================================================================== ---- pjmedia/include/pjmedia/format.h (revision 5249) -+++ pjmedia/include/pjmedia/format.h (working copy) -@@ -97,6 +97,7 @@ - /** - * 32bit RGB with alpha channel - */ -+ PJMEDIA_FORMAT_ARGB = PJMEDIA_FORMAT_PACK('A', 'R', 'G', 'B'), - PJMEDIA_FORMAT_RGBA = PJMEDIA_FORMAT_PACK('R', 'G', 'B', 'A'), - PJMEDIA_FORMAT_BGRA = PJMEDIA_FORMAT_PACK('B', 'G', 'R', 'A'), - -@@ -216,6 +217,7 @@ - PJMEDIA_FORMAT_MPEG2VIDEO = PJMEDIA_FORMAT_PACK('M', 'P', '2', 'V'), - PJMEDIA_FORMAT_MPEG4 = PJMEDIA_FORMAT_PACK('M', 'P', 'G', '4'), - -+ PJMEDIA_FORMAT_VP8 = PJMEDIA_FORMAT_PACK('L', 'V', 'P', '8'), - } pjmedia_format_id; - - /** -Index: pjmedia/include/pjmedia/rtcp.h -=================================================================== ---- pjmedia/include/pjmedia/rtcp.h (revision 5249) -+++ pjmedia/include/pjmedia/rtcp.h (working copy) -@@ -256,6 +256,8 @@ - - pjmedia_rtcp_stat stat; /**< Bidirectional stream stat. */ - -+ pj_bool_t keyframe_requested; /** Set to true when RTCP PLI is received */ -+ - #if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0) - /** - * Specify whether RTCP XR processing is enabled on this session. -@@ -462,6 +464,22 @@ - pj_size_t *length, - const pj_str_t *reason); - -+/** -+ * Build an RTCP PLI packet. This packet can be appended to other RTCP -+ * packets, e.g: RTCP RR/SR, to compose a compound RTCP packet. -+ * -+ * @param session The RTCP session. -+ * @param buf The buffer to receive RTCP PLI packet. -+ * @param length On input, it will contain the buffer length. -+ * On output, it will contain the generated RTCP PLI -+ * packet length. -+ * -+ * @return PJ_SUCCESS on success. -+ */ -+PJ_DECL(pj_status_t) pjmedia_rtcp_build_rtcp_pli( -+ pjmedia_rtcp_session *session, -+ void *buf, -+ pj_size_t *length); - - /** - * Call this function if RTCP XR needs to be enabled/disabled in the -Index: pjmedia/include/pjmedia/signatures.h -=================================================================== ---- pjmedia/include/pjmedia/signatures.h (revision 5249) -+++ pjmedia/include/pjmedia/signatures.h (working copy) -@@ -153,6 +153,7 @@ - #define PJMEDIA_SIG_PORT_ECHO PJMEDIA_SIG_CLASS_PORT_AUD('E','C') - #define PJMEDIA_SIG_PORT_MEM_CAPTURE PJMEDIA_SIG_CLASS_PORT_AUD('M','C') - #define PJMEDIA_SIG_PORT_MEM_PLAYER PJMEDIA_SIG_CLASS_PORT_AUD('M','P') -+#define PJMEDIA_SIG_PORT_MIXER PJMEDIA_SIG_CLASS_PORT_AUD('M','X') - #define PJMEDIA_SIG_PORT_NULL PJMEDIA_SIG_CLASS_PORT_AUD('N','U') - #define PJMEDIA_SIG_PORT_RESAMPLE PJMEDIA_SIG_CLASS_PORT_AUD('R','E') - #define PJMEDIA_SIG_PORT_SPLIT_COMB PJMEDIA_SIG_CLASS_PORT_AUD('S','C') -Index: pjmedia/include/pjmedia/sound_port.h -=================================================================== ---- pjmedia/include/pjmedia/sound_port.h (revision 5249) -+++ pjmedia/include/pjmedia/sound_port.h (working copy) -@@ -330,6 +330,16 @@ - - - /** -+ * Reset the EC state in the sound port. -+ * -+ * @param snd_port The sound device port. -+ * -+ * @return PJ_SUCCESS on success. -+ */ -+PJ_DECL(pj_status_t) pjmedia_snd_port_reset_ec_state(pjmedia_snd_port *snd_port); -+ -+ -+/** - * Connect a port to the sound device port. If the sound device port has a - * sound recorder device, then this will start periodic function call to - * the port's put_frame() function. If the sound device has a sound player -Index: pjmedia/include/pjmedia/transport_ice.h -=================================================================== ---- pjmedia/include/pjmedia/transport_ice.h (revision 5249) -+++ pjmedia/include/pjmedia/transport_ice.h (working copy) -@@ -59,6 +59,28 @@ - pj_ice_strans_op op, - pj_status_t status); - -+ /** -+ * This callback will be called when ICE state changes. -+ * -+ * @param tp PJMEDIA ICE transport. -+ * @param prev Previous state. -+ * @param curr Current state. -+ */ -+ void (*on_ice_state)(pjmedia_transport *tp, -+ pj_ice_strans_state prev, -+ pj_ice_strans_state curr); -+ -+ /** -+ * This callback will be called when ICE is stopped. -+ * -+ * @param tp PJMEDIA ICE transport. -+ * @param reason Reason for stopping ICE. -+ * @param err Error code -+ */ -+ void (*on_ice_stop)(pjmedia_transport *tp, -+ char *reason, -+ pj_status_t err); -+ - } pjmedia_ice_cb; - - -@@ -222,6 +244,16 @@ - pjmedia_transport **p_tp); - - /** -+ * Return the ICE stream transport associated with this PJMEDIA transport -+ * -+ * @param tp Media transport instance. -+ * -+ * @return Pointer to the pj_ice_strans instance associated with this -+ * media transport. -+ */ -+PJ_DECL(pj_ice_strans*) pjmedia_ice_get_strans(pjmedia_transport *tp); -+ -+/** - * Get the group lock for the ICE media transport. - * - * @param tp The ICE media transport. -Index: pjmedia/include/pjmedia/vid_stream.h -=================================================================== ---- pjmedia/include/pjmedia/vid_stream.h (revision 5249) -+++ pjmedia/include/pjmedia/vid_stream.h (working copy) -@@ -414,6 +414,17 @@ - - - /** -+ * Send RTCP PLI for the media stream. -+ * -+ * @param stream The media stream. -+ * -+ * @return PJ_SUCCESS on success. -+ */ -+PJ_DECL(pj_status_t) pjmedia_vid_stream_send_rtcp_pli( -+ pjmedia_vid_stream *stream); -+ -+ -+/** - * @} - */ - -Index: pjmedia/include/pjmedia-audiodev/audiodev.h -=================================================================== ---- pjmedia/include/pjmedia-audiodev/audiodev.h (revision 5249) -+++ pjmedia/include/pjmedia-audiodev/audiodev.h (working copy) -@@ -29,6 +29,7 @@ - #include - #include - #include -+#include - #include - - -@@ -437,6 +438,28 @@ - } pjmedia_aud_param; - - -+typedef enum pjmedia_aud_dev_event { -+ PJMEDIA_AUD_DEV_DEFAULT_INPUT_CHANGED, -+ PJMEDIA_AUD_DEV_DEFAULT_OUTPUT_CHANGED, -+ PJMEDIA_AUD_DEV_LIST_WILL_REFRESH, -+ PJMEDIA_AUD_DEV_LIST_DID_REFRESH -+} pjmedia_aud_dev_event; -+ -+ -+typedef void (*pjmedia_aud_dev_observer_callback)(pjmedia_aud_dev_event event); -+ -+/** -+ * This structure specifies the parameters to set an audio device observer -+ */ -+typedef struct pjmedia_aud_dev_observer { -+ pjmedia_aud_dev_observer_callback cb; -+ pj_pool_t *pool; -+ pj_mutex_t *lock; -+ pj_thread_t *thread; -+ pj_thread_desc thread_desc; -+} pjmedia_aud_dev_observer; -+ -+ - /** Forward declaration for pjmedia_aud_stream */ - typedef struct pjmedia_aud_stream pjmedia_aud_stream; - -@@ -717,7 +740,19 @@ - */ - PJ_DECL(pj_status_t) pjmedia_aud_stream_destroy(pjmedia_aud_stream *strm); - -+/** -+ * Set an audio device observer callback. -+ * -+ * @param cb The callback that needs to be registred, or NULL in -+ * in case it needs to be unregistered. Only one callback -+ * can be registered. -+ * -+ * @return PJ_SUCCESS on successful operation or the appropriate -+ * error code. -+ */ -+PJ_DECL(pj_status_t) pjmedia_aud_dev_set_observer_cb(pjmedia_aud_dev_observer_callback cb); - -+ - /** - * @} - */ -Index: pjmedia/include/pjmedia-audiodev/audiodev_imp.h -=================================================================== ---- pjmedia/include/pjmedia-audiodev/audiodev_imp.h (revision 5249) -+++ pjmedia/include/pjmedia-audiodev/audiodev_imp.h (working copy) -@@ -29,6 +29,14 @@ - * @{ - */ - -+typedef enum pjmedia_aud_dev_change_event { -+ DEFAULT_INPUT_CHANGED = 1, -+ DEFAULT_OUTPUT_CHANGED, -+ DEVICE_LIST_CHANGED -+} pjmedia_aud_dev_change_event; -+ -+typedef void (*pjmedia_aud_dev_change_callback)(pjmedia_aud_dev_change_event event); -+ - /** - * Sound device factory operations. - */ -@@ -99,6 +107,29 @@ - */ - pj_status_t (*refresh)(pjmedia_aud_dev_factory *f); - -+ /** -+ * Set audio device change callback -+ * -+ * @param f The audio device factory. -+ * @param cb The audio device change callback. -+ */ -+ void (*set_dev_change_cb)(pjmedia_aud_dev_factory *f, -+ pjmedia_aud_dev_change_callback cb); -+ -+ /** -+ * Get default recording device index -+ * -+ * @param f The audio device factory. -+ */ -+ int (*get_default_rec_dev)(pjmedia_aud_dev_factory *f); -+ -+ /** -+ * Get default playback device index -+ * -+ * @param f The audio device factory. -+ */ -+ int (*get_default_play_dev)(pjmedia_aud_dev_factory *f); -+ - } pjmedia_aud_dev_factory_op; - - -Index: pjmedia/include/pjmedia-audiodev/config.h -=================================================================== ---- pjmedia/include/pjmedia-audiodev/config.h (revision 5249) -+++ pjmedia/include/pjmedia-audiodev/config.h (working copy) -@@ -43,60 +43,6 @@ - */ - - /** -- * This setting controls the buffer length of audio device name. -- * -- * Default: 128 for Windows platforms, 64 for others -- */ --#ifndef PJMEDIA_AUD_DEV_INFO_NAME_LEN --# if (defined(PJ_WIN32) && PJ_WIN32!=0) || \ -- (defined(PJ_WIN64) && PJ_WIN64!=0) --# define PJMEDIA_AUD_DEV_INFO_NAME_LEN 128 --# else --# define PJMEDIA_AUD_DEV_INFO_NAME_LEN 64 --# endif --#endif -- --/** -- * This setting controls whether PortAudio support should be included. -- * -- * By default it is enabled except on Windows platforms (including -- * Windows Mobile) and Symbian. -- */ --#ifndef PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO --# if (defined(PJ_WIN32) && PJ_WIN32!=0) || \ -- (defined(PJ_WIN64) && PJ_WIN64!=0) || \ -- (defined(PJ_SYMBIAN) && PJ_SYMBIAN!=0) --# define PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO 0 --# else --# define PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO 1 --# endif --#endif -- --/** -- * This setting controls whether Android OpenSL audio support should be -- * included. -- */ --#ifndef PJMEDIA_AUDIO_DEV_HAS_OPENSL --# define PJMEDIA_AUDIO_DEV_HAS_OPENSL 0 --#endif -- --/** -- * This setting controls whether Android JNI audio support should be -- * included. -- */ --#ifndef PJMEDIA_AUDIO_DEV_HAS_ANDROID_JNI --# define PJMEDIA_AUDIO_DEV_HAS_ANDROID_JNI 0 --#endif -- --/** -- * This setting controls whether BlackBerry 10 (BB10) audio support -- * should be included. -- */ --#ifndef PJMEDIA_AUDIO_DEV_HAS_BB10 --# define PJMEDIA_AUDIO_DEV_HAS_BB10 0 --#endif -- --/** - * This setting controls whether native ALSA support should be included. - */ - #ifndef PJMEDIA_AUDIO_DEV_HAS_ALSA -@@ -124,107 +70,26 @@ - * This setting controls whether WMME support should be included. - */ - #ifndef PJMEDIA_AUDIO_DEV_HAS_WMME --# define PJMEDIA_AUDIO_DEV_HAS_WMME 1 -+# define PJMEDIA_AUDIO_DEV_HAS_WMME 0 - #endif - -- --/** -- * This setting controls whether BDIMAD support should be included. -- */ --#ifndef PJMEDIA_AUDIO_DEV_HAS_BDIMAD --# define PJMEDIA_AUDIO_DEV_HAS_BDIMAD 0 --#endif - -- - /** -- * This setting controls whether Symbian APS support should be included. -- */ --#ifndef PJMEDIA_AUDIO_DEV_HAS_SYMB_APS --# define PJMEDIA_AUDIO_DEV_HAS_SYMB_APS 0 --#endif -- -- --/** -- * This setting controls whether Symbian APS should perform codec -- * detection in its factory initalization. Note that codec detection -- * may take few seconds and detecting more codecs will take more time. -- * Possible values are: -- * - 0: no codec detection, all APS codec (AMR-NB, G.711, G.729, and -- * iLBC) will be assumed as supported. -- * - 1: minimal codec detection, i.e: only detect for AMR-NB and G.711, -- * (G.729 and iLBC are considered to be supported/unsupported when -- * G.711 is supported/unsupported). -- * - 2: full codec detection, i.e: detect AMR-NB, G.711, G.729, and iLBC. -- * -- * Default: 1 (minimal codec detection) -- */ --#ifndef PJMEDIA_AUDIO_DEV_SYMB_APS_DETECTS_CODEC --# define PJMEDIA_AUDIO_DEV_SYMB_APS_DETECTS_CODEC 1 --#endif -- -- --/** -- * This setting controls whether Symbian VAS support should be included. -- */ --#ifndef PJMEDIA_AUDIO_DEV_HAS_SYMB_VAS --# define PJMEDIA_AUDIO_DEV_HAS_SYMB_VAS 0 --#endif -- --/** -- * This setting controls Symbian VAS version to be used. Currently, valid -- * values are only 1 (for VAS 1.0) and 2 (for VAS 2.0). -+ * This setting controls the buffer length of audio device name. - * -- * Default: 1 (VAS version 1.0) -+ * Default: 128 for Windows platforms, 64 for others - */ --#ifndef PJMEDIA_AUDIO_DEV_SYMB_VAS_VERSION --# define PJMEDIA_AUDIO_DEV_SYMB_VAS_VERSION 1 -+#ifndef PJMEDIA_AUD_DEV_INFO_NAME_LEN -+# if (defined(PJ_WIN32) && PJ_WIN32!=0) || \ -+ (defined(PJ_WIN64) && PJ_WIN64!=0) -+# define PJMEDIA_AUD_DEV_INFO_NAME_LEN 128 -+# else -+# define PJMEDIA_AUD_DEV_INFO_NAME_LEN 64 -+# endif - #endif - - - /** -- * This setting controls whether Symbian audio (using built-in multimedia -- * framework) support should be included. -- */ --#ifndef PJMEDIA_AUDIO_DEV_HAS_SYMB_MDA --# define PJMEDIA_AUDIO_DEV_HAS_SYMB_MDA PJ_SYMBIAN --#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 -- * (sound.h). -- * -- * Enable this API if: -- * - you have implemented your own sound device using the old sound -- * device API (sound.h), and -- * - you wish to be able to use your sound device implementation -- * using the new Audio Device API. -- * -- * Please see http://trac.pjsip.org/repos/wiki/Audio_Dev_API for more -- * info. -- */ --#ifndef PJMEDIA_AUDIO_DEV_HAS_LEGACY_DEVICE --# define PJMEDIA_AUDIO_DEV_HAS_LEGACY_DEVICE 0 --#endif -- -- --/** - * @} - */ - -Index: pjmedia/include/pjmedia-audiodev/errno.h -=================================================================== ---- pjmedia/include/pjmedia-audiodev/errno.h (revision 5249) -+++ pjmedia/include/pjmedia-audiodev/errno.h (working copy) -@@ -49,20 +49,6 @@ - - - /** -- * Mapping from PortAudio error codes to pjmedia error space. -- */ --#define PJMEDIA_AUDIODEV_PORTAUDIO_ERRNO_START \ -- (PJMEDIA_AUDIODEV_ERRNO_END-10000) --#define PJMEDIA_AUDIODEV_PORTAUDIO_ERRNO_END \ -- (PJMEDIA_AUDIODEV_PORTAUDIO_ERRNO_START + 10000 -1) --/** -- * Convert PortAudio error code to PJLIB error code. -- * PortAudio error code range: 0 >= err >= -10000 -- */ --#define PJMEDIA_AUDIODEV_ERRNO_FROM_PORTAUDIO(err) \ -- ((int)PJMEDIA_AUDIODEV_PORTAUDIO_ERRNO_START-err) -- --/** - * Mapping from Windows multimedia WaveIn error codes. - */ - #define PJMEDIA_AUDIODEV_WMME_IN_ERROR_START \ -Index: pjmedia/include/pjmedia-codec/config.h -=================================================================== ---- pjmedia/include/pjmedia-codec/config.h (revision 5249) -+++ pjmedia/include/pjmedia-codec/config.h (working copy) -@@ -132,116 +132,14 @@ - - - /** -- * Enable the features provided by Intel IPP libraries, for example -- * codecs such as G.729, G.723.1, G.726, G.728, G.722.1, and AMR. -- * -- * By default this is disabled. Please follow the instructions in -- * http://trac.pjsip.org/repos/wiki/Intel_IPP_Codecs on how to setup -- * Intel IPP with PJMEDIA. -+ * Unless specified otherwise, opus codec is included by default. - */ --#ifndef PJMEDIA_HAS_INTEL_IPP --# define PJMEDIA_HAS_INTEL_IPP 0 -+#ifndef PJMEDIA_HAS_OPUS_CODEC -+# define PJMEDIA_HAS_OPUS_CODEC 1 - #endif - - - /** -- * Visual Studio only: when this option is set, the Intel IPP libraries -- * will be automatically linked to application using pragma(comment) -- * constructs. This is convenient, however it will only link with -- * the stub libraries and the Intel IPP DLL's will be required when -- * distributing the application. -- * -- * If application wants to link with the different types of the Intel IPP -- * libraries (for example, the static libraries), it must set this option -- * to zero and specify the Intel IPP libraries in the application's input -- * library specification manually. -- * -- * Default 1. -- */ --#ifndef PJMEDIA_AUTO_LINK_IPP_LIBS --# define PJMEDIA_AUTO_LINK_IPP_LIBS 1 --#endif -- -- --/** -- * Enable Intel IPP AMR codec. This also needs to be enabled when AMR WB -- * codec is enabled. This option is only used when PJMEDIA_HAS_INTEL_IPP -- * is enabled. -- * -- * Default: 1 -- */ --#ifndef PJMEDIA_HAS_INTEL_IPP_CODEC_AMR --# define PJMEDIA_HAS_INTEL_IPP_CODEC_AMR 1 --#endif -- -- --/** -- * Enable Intel IPP AMR wideband codec. The PJMEDIA_HAS_INTEL_IPP_CODEC_AMR -- * option must also be enabled to use this codec. This option is only used -- * when PJMEDIA_HAS_INTEL_IPP is enabled. -- * -- * Default: 1 -- */ --#ifndef PJMEDIA_HAS_INTEL_IPP_CODEC_AMRWB --# define PJMEDIA_HAS_INTEL_IPP_CODEC_AMRWB 1 --#endif -- -- --/** -- * Enable Intel IPP G.729 codec. This option is only used when -- * PJMEDIA_HAS_INTEL_IPP is enabled. -- * -- * Default: 1 -- */ --#ifndef PJMEDIA_HAS_INTEL_IPP_CODEC_G729 --# define PJMEDIA_HAS_INTEL_IPP_CODEC_G729 1 --#endif -- -- --/** -- * Enable Intel IPP G.723.1 codec. This option is only used when -- * PJMEDIA_HAS_INTEL_IPP is enabled. -- * -- * Default: 1 -- */ --#ifndef PJMEDIA_HAS_INTEL_IPP_CODEC_G723_1 --# define PJMEDIA_HAS_INTEL_IPP_CODEC_G723_1 1 --#endif -- -- --/** -- * Enable Intel IPP G.726 codec. This option is only used when -- * PJMEDIA_HAS_INTEL_IPP is enabled. -- * -- * Default: 1 -- */ --#ifndef PJMEDIA_HAS_INTEL_IPP_CODEC_G726 --# define PJMEDIA_HAS_INTEL_IPP_CODEC_G726 1 --#endif -- -- --/** -- * Enable Intel IPP G.728 codec. This option is only used when -- * PJMEDIA_HAS_INTEL_IPP is enabled. -- * -- * Default: 1 -- */ --#ifndef PJMEDIA_HAS_INTEL_IPP_CODEC_G728 --# define PJMEDIA_HAS_INTEL_IPP_CODEC_G728 1 --#endif -- -- --/** -- * Enable Intel IPP G.722.1 codec. This option is only used when -- * PJMEDIA_HAS_INTEL_IPP is enabled. -- * -- * Default: 1 -- */ --#ifndef PJMEDIA_HAS_INTEL_IPP_CODEC_G722_1 --# define PJMEDIA_HAS_INTEL_IPP_CODEC_G722_1 1 --#endif -- --/** - * Enable Passthrough codecs. - * - * Default: 0 -@@ -251,61 +149,6 @@ - #endif - - /** -- * Enable AMR passthrough codec. -- * -- * Default: 1 -- */ --#ifndef PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR --# define PJMEDIA_HAS_PASSTHROUGH_CODEC_AMR 1 --#endif -- --/** -- * Enable G.729 passthrough codec. -- * -- * Default: 1 -- */ --#ifndef PJMEDIA_HAS_PASSTHROUGH_CODEC_G729 --# define PJMEDIA_HAS_PASSTHROUGH_CODEC_G729 1 --#endif -- --/** -- * Enable iLBC passthrough codec. -- * -- * Default: 1 -- */ --#ifndef PJMEDIA_HAS_PASSTHROUGH_CODEC_ILBC --# define PJMEDIA_HAS_PASSTHROUGH_CODEC_ILBC 1 --#endif -- --/** -- * Enable PCMU passthrough codec. -- * -- * Default: 1 -- */ --#ifndef PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMU --# define PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMU 1 --#endif -- --/** -- * Enable PCMA passthrough codec. -- * -- * Default: 1 -- */ --#ifndef PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMA --# define PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMA 1 --#endif -- --/* If passthrough and PCMU/PCMA are enabled, disable the software -- * G.711 codec -- */ --#if PJMEDIA_HAS_PASSTHROUGH_CODECS && \ -- (PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMU || PJMEDIA_HAS_PASSTHROUGH_CODEC_PCMA) --# undef PJMEDIA_HAS_G711_CODEC --# define PJMEDIA_HAS_G711_CODEC 0 --#endif -- -- --/** - * G.722.1 codec is disabled by default. - */ - #ifndef PJMEDIA_HAS_G7221_CODEC -@@ -313,49 +156,6 @@ - #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 -- --/** -- * Enable OpenCORE AMR-WB codec. -- * See https://trac.pjsip.org/repos/ticket/1608 for some info. -- * -- * Default: 0 -- */ --#ifndef PJMEDIA_HAS_OPENCORE_AMRWB_CODEC --# define PJMEDIA_HAS_OPENCORE_AMRWB_CODEC 0 --#endif -- --/** -- * Link with libopencore-amrXX via pragma comment on Visual Studio. -- * This option only makes sense if PJMEDIA_HAS_OPENCORE_AMRNB/WB_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/WB_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 - * be shifted right by this value, and similarly PCM output samples from -@@ -388,88 +188,6 @@ - - - /** -- * Enable SILK codec. -- * -- * Default: 0 -- */ --#ifndef PJMEDIA_HAS_SILK_CODEC --# define PJMEDIA_HAS_SILK_CODEC 0 --#endif -- -- --/** -- * SILK codec default complexity setting, valid values are 0 (lowest), 1, -- * and 2. -- * -- * Default: 2 -- */ --#ifndef PJMEDIA_CODEC_SILK_DEFAULT_COMPLEXITY --# define PJMEDIA_CODEC_SILK_DEFAULT_COMPLEXITY 2 --#endif -- --/** -- * SILK codec default quality setting, valid values are ranging from -- * 0 (lowest) to 10. Please note that pjsua-lib may override this setting -- * via its codec quality setting (i.e PJSUA_DEFAULT_CODEC_QUALITY). -- * -- * Default: 10 -- */ --#ifndef PJMEDIA_CODEC_SILK_DEFAULT_QUALITY --# define PJMEDIA_CODEC_SILK_DEFAULT_QUALITY 10 --#endif -- -- --/** -- * Enable OPUS codec. -- * -- * Default: 0 -- */ --#ifndef PJMEDIA_HAS_OPUS_CODEC --# define PJMEDIA_HAS_OPUS_CODEC 0 --#endif -- --/** -- * OPUS codec sample rate. -- * -- * Default: 48000 -- */ --#ifndef PJMEDIA_CODEC_OPUS_DEFAULT_SAMPLE_RATE --# define PJMEDIA_CODEC_OPUS_DEFAULT_SAMPLE_RATE 48000 --#endif -- --/** -- * OPUS codec default maximum average bit rate. -- * -- * Default: 0 (leave it to default value specified by Opus, which will -- * take into account factors such as media content (speech/music), sample -- * rate, channel count, etc). -- */ --#ifndef PJMEDIA_CODEC_OPUS_DEFAULT_BIT_RATE --# define PJMEDIA_CODEC_OPUS_DEFAULT_BIT_RATE 0 --#endif -- -- --/** -- * OPUS default encoding complexity, which is an integer from -- * 0 to 10, where 0 is the lowest complexity and 10 is the highest. -- * -- * Default: 5 -- */ --#ifndef PJMEDIA_CODEC_OPUS_DEFAULT_COMPLEXITY --# define PJMEDIA_CODEC_OPUS_DEFAULT_COMPLEXITY 5 --#endif -- -- --/** -- * OPUS default CBR (constant bit rate) setting -- * -- * Default: PJ_FALSE (which means Opus will use VBR (variable bit rate)) -- */ --#ifndef PJMEDIA_CODEC_OPUS_DEFAULT_CBR --# define PJMEDIA_CODEC_OPUS_DEFAULT_CBR PJ_FALSE --#endif -- --/** - * Specify if FFMPEG codecs are available. - * - * Default: PJMEDIA_HAS_LIBAVCODEC -@@ -507,6 +225,14 @@ - #endif - - /** -+ * Compile VPX support, unless explicitly disabled -+ */ -+#ifndef PJMEDIA_HAS_VPX_CODEC -+# define PJMEDIA_HAS_VPX_CODEC PJMEDIA_HAS_LIBVPX -+#endif -+ -+ -+/** - * @} - */ - -Index: pjmedia/include/pjmedia-codec/config_auto.h.in -=================================================================== ---- pjmedia/include/pjmedia-codec/config_auto.h.in (revision 5249) -+++ pjmedia/include/pjmedia-codec/config_auto.h.in (working copy) -@@ -31,28 +31,17 @@ - * including the setting as detected by autoconf. - */ - --/* L16 codec */ --#ifndef PJMEDIA_HAS_L16_CODEC --#undef PJMEDIA_HAS_L16_CODEC --#endif - -- - /* GSM codec */ - #ifndef PJMEDIA_HAS_GSM_CODEC - #undef PJMEDIA_HAS_GSM_CODEC - #endif - --#undef PJMEDIA_EXTERNAL_GSM_CODEC --#undef PJMEDIA_EXTERNAL_GSM_GSM_H --#undef PJMEDIA_EXTERNAL_GSM_H -- - /* Speex codec */ - #ifndef PJMEDIA_HAS_SPEEX_CODEC - #undef PJMEDIA_HAS_SPEEX_CODEC - #endif - --#undef PJMEDIA_EXTERNAL_SPEEX_CODEC -- - /* iLBC codec */ - #ifndef PJMEDIA_HAS_ILBC_CODEC - #undef PJMEDIA_HAS_ILBC_CODEC -@@ -69,26 +58,6 @@ - #undef PJMEDIA_HAS_G7221_CODEC - #endif - --/* OpenCORE AMR-NB codec */ --#ifndef PJMEDIA_HAS_OPENCORE_AMRNB_CODEC --#undef PJMEDIA_HAS_OPENCORE_AMRNB_CODEC --#endif -- --/* OpenCORE AMR-WB codec */ --#ifndef PJMEDIA_HAS_OPENCORE_AMRWB_CODEC --#undef PJMEDIA_HAS_OPENCORE_AMRWB_CODEC --#endif -- --/* SILK codec */ --#ifndef PJMEDIA_HAS_SILK_CODEC --#undef PJMEDIA_HAS_SILK_CODEC --#endif -- --/* OPUS codec */ --#ifndef PJMEDIA_HAS_OPUS_CODEC --#undef PJMEDIA_HAS_OPUS_CODEC --#endif -- - #endif /* __PJMEDIA_CODEC_CONFIG_AUTO_H_ */ - - -Index: pjmedia/include/pjmedia-codec/opus.h -=================================================================== ---- pjmedia/include/pjmedia-codec/opus.h (revision 5249) -+++ pjmedia/include/pjmedia-codec/opus.h (working copy) -@@ -1,155 +1,25 @@ --/* $Id$ */ --/* -- * Copyright (C) 2015-2016 Teluu Inc. (http://www.teluu.com) -- * Copyright (C) 2012-2015 Zaark Technology AB -- * -- * 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 -- */ --/* This file is the header of Opus codec wrapper and was contributed by -- * Zaark Technology AB -- */ - --#ifndef __PJMEDIA_CODEC_OPUS_H__ --#define __PJMEDIA_CODEC_OPUS_H__ -+#ifndef __PJMEDIA_CODEC_OPUS_CODEC_H__ -+#define __PJMEDIA_CODEC_OPUS_CODEC_H__ - - /** -- * @file opus.h -- * @brief Opus codec. -+ * @file pj_opus.h -+ * @brief OPUS codec. - */ - - #include - -+ - PJ_BEGIN_DECL - --/** -- * @defgroup PJMED_OPUS Opus Codec Family -- * @ingroup PJMEDIA_CODEC_CODECS -- * @brief Opus codec wrapper -- * @{ -- * -- * This section describes functions to initialize and register Opus codec -- * factory to the codec manager. After the codec factory has been registered, -- * application can use @ref PJMEDIA_CODEC API to manipulate the codec. -- * -- * Opus codec uses multiple bit rates, and supports fullband (48 kHz -- * sampling rate), super wideband (24 kHz sampling rate), wideband (16 kHz -- * sampling rate), medium band (12kHz sampling rate), and narrowband -- * (8 kHz sampling rate). -- * -- * -- * \section codec_setting Codec Settings -- * -- * General codec settings for this codec such as VAD and PLC can be -- * manipulated through the setting field in #pjmedia_codec_param -- * (see the documentation of #pjmedia_codec_param for more info). -- * -- * For Opus codec specific settings, such as sample rate, -- * channel count, bit rate, complexity, and CBR, can be configured -- * in #pjmedia_codec_opus_config. -- * The default setting of sample rate is specified in -- * #PJMEDIA_CODEC_OPUS_DEFAULT_SAMPLE_RATE. The default setting of -- * bitrate is specified in #PJMEDIA_CODEC_OPUS_DEFAULT_BIT_RATE. -- * And the default setting of complexity is specified in -- * #PJMEDIA_CODEC_OPUS_DEFAULT_COMPLEXITY. -- * -- * After modifying any of these settings, application needs to call -- * #pjmedia_codec_opus_set_default_param(), which will generate the -- * appropriate decoding fmtp attributes. -- * -- * Here is an example of modifying the codec settings: -- \code -- pjmedia_codec_param param; -- pjmedia_codec_opus_config opus_cfg; -+PJ_DECL(pj_status_t) pjmedia_codec_opus_init( pjmedia_endpt *endpt); -+PJ_DECL(pj_status_t) pjmedia_codec_opus_deinit(void); - -- pjmedia_codec_mgr_get_default_param(.., ¶m); -- pjmedia_codec_opus_get_config(&opus_cfg); -- ... -- // Set VAD -- param.setting.vad = 1; -- // Set PLC -- param.setting.vad = 1; -- // Set sample rate -- opus_cfg.sample_rate = 16000; -- // Set channel count -- opus_cfg.channel_cnt = 2; -- // Set bit rate -- opus_cfg.bit_rate = 20000; -- ... -- pjmedia_codec_opus_set_default_param(&opus_cfg, ¶m); -- \endcode -- * -- */ -+PJ_END_DECL - --/** -- * Opus codec configuration. -- */ --typedef struct pjmedia_codec_opus_config --{ -- unsigned sample_rate; /**< Sample rate in Hz. */ -- unsigned channel_cnt; /**< Number of channels. */ -- unsigned bit_rate; /**< Encoder bit rate in bps. */ -- unsigned packet_loss; /**< Encoder's expected packet loss pct. */ -- unsigned complexity; /**< Encoder complexity, 0-10(10 is highest)*/ -- pj_bool_t cbr; /**< Constant bit rate? */ --} pjmedia_codec_opus_config; - -- - /** -- * Initialize and register Opus codec factory to pjmedia endpoint. -- * -- * @param endpt The pjmedia endpoint. -- * -- * @return PJ_SUCCESS on success. -- */ --PJ_DECL(pj_status_t) pjmedia_codec_opus_init( pjmedia_endpt *endpt ); -- --/** -- * Unregister Opus codec factory from pjmedia endpoint and deinitialize -- * the Opus codec library. -- * -- * @return PJ_SUCCESS on success. -- */ --PJ_DECL(pj_status_t) pjmedia_codec_opus_deinit( void ); -- --/** -- * Get the default Opus configuration. -- * -- * @param cfg Opus codec configuration. -- * -- * @return PJ_SUCCESS on success. -- */ --PJ_DECL(pj_status_t) --pjmedia_codec_opus_get_config( pjmedia_codec_opus_config *cfg ); -- --/** -- * Set the default Opus configuration and set the default Opus codec param. -- * Note that the function will call #pjmedia_codec_mgr_set_default_param(). -- * -- * @param cfg Opus codec configuration. -- * @param param The new default Opus codec parameter. -- * -- * @return PJ_SUCCESS on success. -- */ --PJ_DECL(pj_status_t) --pjmedia_codec_opus_set_default_param(const pjmedia_codec_opus_config *cfg, -- pjmedia_codec_param *param ); -- --PJ_END_DECL -- --/** - * @} - */ - --#endif /* __PJMEDIA_CODEC_OPUS_H__ */ -+#endif /* __PJMEDIA_CODEC_OPUS_CODEC_H__ */ -Index: pjmedia/include/pjmedia-codec/types.h -=================================================================== ---- pjmedia/include/pjmedia-codec/types.h (revision 5249) -+++ pjmedia/include/pjmedia-codec/types.h (working copy) -@@ -71,6 +71,7 @@ - PJMEDIA_RTP_PT_AMR, /**< AMR (4.75 - 12.2Kbps) */ - PJMEDIA_RTP_PT_AMRWB, /**< AMRWB (6.6 - 23.85Kbps)*/ - PJMEDIA_RTP_PT_AMRWBE, /**< AMRWBE */ -+ PJMEDIA_RTP_PT_OPUS, /**< OPUS */ - PJMEDIA_RTP_PT_G726_16, /**< G726 @ 16Kbps */ - PJMEDIA_RTP_PT_G726_24, /**< G726 @ 24Kbps */ - PJMEDIA_RTP_PT_G726_32, /**< G726 @ 32Kbps */ -@@ -83,7 +84,6 @@ - PJMEDIA_RTP_PT_G7221C_48, /**< G722.1 Annex C (48Kbps)*/ - PJMEDIA_RTP_PT_G7221_RSV1, /**< G722.1 reserve */ - PJMEDIA_RTP_PT_G7221_RSV2, /**< G722.1 reserve */ -- PJMEDIA_RTP_PT_OPUS, /**< OPUS */ - PJMEDIA_RTP_PT_L16_8KHZ_MONO, /**< L16 @ 8KHz, mono */ - PJMEDIA_RTP_PT_L16_8KHZ_STEREO, /**< L16 @ 8KHz, stereo */ - //PJMEDIA_RTP_PT_L16_11KHZ_MONO, /**< L16 @ 11KHz, mono */ -@@ -116,6 +116,7 @@ - PJMEDIA_RTP_PT_H264_RSV2, - PJMEDIA_RTP_PT_H264_RSV3, - PJMEDIA_RTP_PT_H264_RSV4, -+ PJMEDIA_RTP_PT_VP8, - - /* Caution! - * Ensure the value of the last pt above is <= 127. -Index: pjmedia/include/pjmedia-codec.h -=================================================================== ---- pjmedia/include/pjmedia-codec.h (revision 5249) -+++ pjmedia/include/pjmedia-codec.h (working copy) -@@ -26,18 +26,15 @@ - */ - - #include --#include - #include -+#include - #include - #include - #include - #include - #include --#include --#include - #include - #include --#include - #include - - -Index: pjmedia/include/pjmedia-videodev/config.h -=================================================================== ---- pjmedia/include/pjmedia-videodev/config.h (revision 5249) -+++ pjmedia/include/pjmedia-videodev/config.h (working copy) -@@ -74,95 +74,16 @@ - - - /** -- * This setting controls whether OpenGL for iOS should be included. -+ * This setting controls whether AVFoundation support should be included. - * - * Default: 0 (or detected by configure) - */ --#ifndef PJMEDIA_VIDEO_DEV_HAS_IOS_OPENGL --# define PJMEDIA_VIDEO_DEV_HAS_IOS_OPENGL 0 --#else --# if defined(PJMEDIA_VIDEO_DEV_HAS_IOS_OPENGL) && \ -- PJMEDIA_VIDEO_DEV_HAS_IOS_OPENGL != 0 --# undef PJMEDIA_VIDEO_DEV_HAS_OPENGL_ES --# define PJMEDIA_VIDEO_DEV_HAS_OPENGL_ES 1 --# endif -+#ifndef PJMEDIA_VIDEO_DEV_HAS_AVF -+# define PJMEDIA_VIDEO_DEV_HAS_AVF 0 - #endif - - - /** -- * This setting controls whether OpenGL for Android should be included. -- * -- * Default: 0 (or detected by configure) -- */ --#ifndef PJMEDIA_VIDEO_DEV_HAS_ANDROID_OPENGL --# define PJMEDIA_VIDEO_DEV_HAS_ANDROID_OPENGL 0 --#else --# if defined(PJMEDIA_VIDEO_DEV_HAS_ANDROID_OPENGL) && \ -- PJMEDIA_VIDEO_DEV_HAS_ANDROID_OPENGL != 0 --# undef PJMEDIA_VIDEO_DEV_HAS_OPENGL_ES --# define PJMEDIA_VIDEO_DEV_HAS_OPENGL_ES 1 --# endif --#endif -- -- --/** -- * This setting controls whether OpenGL ES support should be included. -- * -- * Default: 0 (or detected by configure) -- */ --#ifndef PJMEDIA_VIDEO_DEV_HAS_OPENGL_ES --# define PJMEDIA_VIDEO_DEV_HAS_OPENGL_ES 0 --#else --# if defined(PJMEDIA_VIDEO_DEV_HAS_OPENGL_ES) && \ -- PJMEDIA_VIDEO_DEV_HAS_OPENGL_ES != 0 --# undef PJMEDIA_VIDEO_DEV_HAS_OPENGL --# define PJMEDIA_VIDEO_DEV_HAS_OPENGL 1 --# endif --#endif -- -- --/** -- * This setting controls whether OpenGL support should be included. -- * -- * Default: 0 (or detected by configure) -- */ --#ifndef PJMEDIA_VIDEO_DEV_HAS_OPENGL --# define PJMEDIA_VIDEO_DEV_HAS_OPENGL 0 --#endif -- -- --/** -- * This setting controls whether SDL support should be included. -- * -- * Default: 0 (or detected by configure) -- */ --#ifndef PJMEDIA_VIDEO_DEV_HAS_SDL --# define PJMEDIA_VIDEO_DEV_HAS_SDL 0 --# define PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL 0 --#endif -- -- --/** -- * This setting controls whether QT support should be included. -- * -- * Default: 0 (or detected by configure) -- */ --#ifndef PJMEDIA_VIDEO_DEV_HAS_QT --# define PJMEDIA_VIDEO_DEV_HAS_QT 0 --#endif -- -- --/** -- * This setting controls whether IOS support should be included. -- * -- * Default: 0 (or detected by configure) -- */ --#ifndef PJMEDIA_VIDEO_DEV_HAS_IOS --# define PJMEDIA_VIDEO_DEV_HAS_IOS 0 --#endif -- -- --/** - * This setting controls whether Direct Show support should be included. - * - * Default: 0 (unfinished) -@@ -183,16 +104,6 @@ - - - /** -- * This setting controls whether ffmpeg support should be included. -- * -- * Default: 0 (unfinished) -- */ --#ifndef PJMEDIA_VIDEO_DEV_HAS_FFMPEG --# define PJMEDIA_VIDEO_DEV_HAS_FFMPEG 0 --#endif -- -- --/** - * Video4Linux2 - * - * Default: 0 (or detected by configure) -@@ -211,29 +122,16 @@ - # define PJMEDIA_VIDEO_DEV_HAS_AVI 1 - #endif - -- - /** -- * This setting controls whether Android support should be included. -+ * Enable support for frame buffer render device. - * -- * Default: 0 (or detected by configure) -+ * Default: 1 - */ --#ifndef PJMEDIA_VIDEO_DEV_HAS_ANDROID --# define PJMEDIA_VIDEO_DEV_HAS_ANDROID 0 -+#ifndef PJMEDIA_VIDEO_DEV_HAS_FB -+# define PJMEDIA_VIDEO_DEV_HAS_FB 1 - #endif - - --/** -- * Specify the SDL library name to be linked with Visual Studio project. -- * By default, the name is autodetected based on SDL version ("sdl.lib" or -- * "sdl2.lib"), but application may explicitly specify the library name if this -- * autodetection fails. Common names are: "sdl2.lib" or "sdl.lib". -- * -- * Default: undeclared. -- */ --#ifndef PJMEDIA_SDL_LIB --# undef PJMEDIA_SDL_LIB --#endif -- - #endif /* defined(PJMEDIA_HAS_VIDEO) && (PJMEDIA_HAS_VIDEO != 0) */ - - /** -Index: pjmedia/include/pjmedia.h -=================================================================== ---- pjmedia/include/pjmedia.h (revision 5249) -+++ pjmedia/include/pjmedia.h (working copy) -@@ -44,6 +44,7 @@ - #include - #include - #include -+#include - #include - #include - #include -@@ -67,6 +68,7 @@ - #include - #include - #include -+#include - #include - #include - #include -Index: pjmedia/include/pjmedia_audiodev.h -=================================================================== ---- pjmedia/include/pjmedia_audiodev.h (revision 5249) -+++ pjmedia/include/pjmedia_audiodev.h (working copy) -@@ -27,7 +27,6 @@ - - #include - #include --#include - - #endif /* __PJMEDIA_AUDIODEV_H__ */ - -Index: pjmedia/include/pjmedia_videodev.h -=================================================================== ---- pjmedia/include/pjmedia_videodev.h (revision 5249) -+++ pjmedia/include/pjmedia_videodev.h (working copy) -@@ -27,5 +27,6 @@ - #include - #include - #include -+#include - - #endif /* __PJMEDIA_VIDEODEV_H__ */ -Index: pjmedia/src/pjmedia/converter.c -=================================================================== ---- pjmedia/src/pjmedia/converter.c (revision 5249) -+++ pjmedia/src/pjmedia/converter.c (working copy) -@@ -19,6 +19,7 @@ - #include - #include - #include -+#include - - #define THIS_FILE "converter.c" - -@@ -119,7 +120,7 @@ - - pf = mgr->factory_list.next; - while (pf != &mgr->factory_list) { -- if (pf->priority > factory->priority) -+ if (pf->priority < factory->priority) - break; - pf = pf->next; - } -@@ -170,8 +171,25 @@ - if (status != PJ_SUCCESS) - return status; - -+ if (param->src.type == PJMEDIA_TYPE_VIDEO) { -+ char src_fourcc_name[5]; -+ char dst_fourcc_name[5]; -+ PJ_LOG(4, (THIS_FILE, "Converter %p (%s) created for video: %dx%d %s -> %dx%d %s", -+ cv, -+ f->name, -+ param->src.det.vid.size.w, -+ param->src.det.vid.size.h, -+ pjmedia_fourcc_name(param->src.id, src_fourcc_name), -+ param->dst.det.vid.size.w, -+ param->dst.det.vid.size.h, -+ pjmedia_fourcc_name(param->dst.id, dst_fourcc_name))); -+ } else if (param->src.type == PJMEDIA_TYPE_AUDIO) { -+ PJ_LOG(4, (THIS_FILE, "Converter %p created for audio", cv)); -+ } else { -+ PJ_LOG(4, (THIS_FILE, "Converter %p created for unknown", cv)); -+ } -+ - *p_cv = cv; -- - return PJ_SUCCESS; - } - -@@ -184,6 +202,7 @@ - - PJ_DEF(void) pjmedia_converter_destroy(pjmedia_converter *cv) - { -+ PJ_LOG(4, (THIS_FILE, "Converter %p destroyed", cv)); - (*cv->op->destroy)(cv); - } - -Index: pjmedia/src/pjmedia/echo_common.c -=================================================================== ---- pjmedia/src/pjmedia/echo_common.c (revision 5249) -+++ pjmedia/src/pjmedia/echo_common.c (working copy) -@@ -124,13 +124,14 @@ - }; - #endif - -+ - /* -- * WebRTC AEC prototypes -+ * WebRTC prototypes - */ - #if defined(PJMEDIA_HAS_WEBRTC_AEC) && PJMEDIA_HAS_WEBRTC_AEC!=0 - static struct ec_operations webrtc_aec_op = - { -- "WebRTC AEC", -+ "WEBRTC AEC", - &webrtc_aec_create, - &webrtc_aec_destroy, - &webrtc_aec_reset, -@@ -138,6 +139,7 @@ - }; - #endif - -+ - /* - * Create the echo canceller. - */ -@@ -191,6 +193,13 @@ - ec->op = &speex_aec_op; - #endif - -+#if defined(PJMEDIA_HAS_WEBRTC_AEC) && PJMEDIA_HAS_WEBRTC_AEC!=0 -+ } else if ((options & PJMEDIA_ECHO_ALGO_MASK) == PJMEDIA_ECHO_WEBRTC || -+ (options & PJMEDIA_ECHO_ALGO_MASK) == PJMEDIA_ECHO_DEFAULT) -+ { -+ ec->op = &webrtc_aec_op; -+#endif -+ - #if defined(PJMEDIA_HAS_INTEL_IPP_AEC) && PJMEDIA_HAS_INTEL_IPP_AEC!=0 - } else if ((options & PJMEDIA_ECHO_ALGO_MASK) == PJMEDIA_ECHO_IPP || - (options & PJMEDIA_ECHO_ALGO_MASK) == PJMEDIA_ECHO_DEFAULT) -@@ -199,13 +208,6 @@ - - #endif - --#if defined(PJMEDIA_HAS_WEBRTC_AEC) && PJMEDIA_HAS_WEBRTC_AEC!=0 -- } else if ((options & PJMEDIA_ECHO_ALGO_MASK) == PJMEDIA_ECHO_WEBRTC || -- (options & PJMEDIA_ECHO_ALGO_MASK) == PJMEDIA_ECHO_DEFAULT) -- { -- ec->op = &webrtc_aec_op; --#endif -- - } else { - ec->op = &echo_supp_op; - } -Index: pjmedia/src/pjmedia/echo_internal.h -=================================================================== ---- pjmedia/src/pjmedia/echo_internal.h (revision 5249) -+++ pjmedia/src/pjmedia/echo_internal.h (working copy) -@@ -78,21 +78,20 @@ - void *reserved ); - - PJ_DECL(pj_status_t) webrtc_aec_create(pj_pool_t *pool, -- unsigned clock_rate, -- unsigned channel_count, -- unsigned samples_per_frame, -- unsigned tail_ms, -- unsigned options, -- void **p_echo ); -+ unsigned clock_rate, -+ unsigned channel_count, -+ unsigned samples_per_frame, -+ unsigned tail_ms, -+ unsigned options, -+ void **p_echo ); - PJ_DECL(pj_status_t) webrtc_aec_destroy(void *state ); - PJ_DECL(void) webrtc_aec_reset(void *state ); - PJ_DECL(pj_status_t) webrtc_aec_cancel_echo(void *state, -- pj_int16_t *rec_frm, -- const pj_int16_t *play_frm, -- unsigned options, -- void *reserved ); -+ pj_int16_t *rec_frm, -+ const pj_int16_t *play_frm, -+ unsigned options, -+ void *reserved ); - -- - PJ_END_DECL - - #endif -Index: pjmedia/src/pjmedia/endpoint.c -=================================================================== ---- pjmedia/src/pjmedia/endpoint.c (revision 5249) -+++ pjmedia/src/pjmedia/endpoint.c (working copy) -@@ -568,6 +568,7 @@ - /* Put bandwidth info in media level using bandwidth modifier "TIAS" - * (RFC3890). - */ -+#if 0 - if (max_bitrate && pjmedia_add_bandwidth_tias_in_sdp) { - const pj_str_t STR_BANDW_MODIFIER = { "TIAS", 4 }; - pjmedia_sdp_bandw *b; -@@ -577,6 +578,7 @@ - b->value = max_bitrate; - m->bandw[m->bandw_count++] = b; - } -+#endif - - *p_m = m; - return PJ_SUCCESS; -Index: pjmedia/src/pjmedia/errno.c -=================================================================== ---- pjmedia/src/pjmedia/errno.c (revision 5249) -+++ pjmedia/src/pjmedia/errno.c (working copy) -@@ -20,10 +20,6 @@ - #include - #include - #include --#if defined(PJMEDIA_SOUND_IMPLEMENTATION) && \ -- PJMEDIA_SOUND_IMPLEMENTATION == PJMEDIA_SOUND_PORTAUDIO_SOUND --# include --#endif - - #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0) - PJ_BEGIN_DECL -@@ -183,27 +179,6 @@ - - #if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0) - -- /* See if the error comes from PortAudio. */ --#if defined(PJMEDIA_SOUND_IMPLEMENTATION) && \ -- PJMEDIA_SOUND_IMPLEMENTATION == PJMEDIA_SOUND_PORTAUDIO_SOUND -- if (statcode >= PJMEDIA_PORTAUDIO_ERRNO_START && -- statcode <= PJMEDIA_PORTAUDIO_ERRNO_END) -- { -- -- //int pa_err = statcode - PJMEDIA_ERRNO_FROM_PORTAUDIO(0); -- int pa_err = PJMEDIA_PORTAUDIO_ERRNO_START - statcode; -- pj_str_t msg; -- -- msg.ptr = (char*)Pa_GetErrorText(pa_err); -- msg.slen = pj_ansi_strlen(msg.ptr); -- -- errstr.ptr = buf; -- pj_strncpy_with_null(&errstr, &msg, bufsize); -- return errstr; -- -- } else --#endif /* PJMEDIA_SOUND_IMPLEMENTATION */ -- - #if defined(PJMEDIA_HAS_SRTP) && (PJMEDIA_HAS_SRTP != 0) - /* LIBSRTP error */ - if (statcode >= PJMEDIA_LIBSRTP_ERRNO_START && -Index: pjmedia/src/pjmedia/ffmpeg_util.c -=================================================================== ---- pjmedia/src/pjmedia/ffmpeg_util.c (revision 5249) -+++ pjmedia/src/pjmedia/ffmpeg_util.c (working copy) -@@ -26,6 +26,7 @@ - #include "ffmpeg_util.h" - #include - -+ - /* Conversion table between pjmedia_format_id and AVPixelFormat */ - static const struct ffmpeg_fmt_table_t - { -@@ -33,10 +34,11 @@ - enum AVPixelFormat pf; - } ffmpeg_fmt_table[] = - { -+ { PJMEDIA_FORMAT_ARGB, AV_PIX_FMT_ARGB}, - { PJMEDIA_FORMAT_RGBA, AV_PIX_FMT_RGBA}, - { PJMEDIA_FORMAT_RGB24,AV_PIX_FMT_BGR24}, - { PJMEDIA_FORMAT_BGRA, AV_PIX_FMT_BGRA}, -- { PJMEDIA_FORMAT_GBRP, AV_PIX_FMT_GBRP}, -+ { PJMEDIA_FORMAT_GBRP, AV_PIX_FMT_GBR24P}, - - { PJMEDIA_FORMAT_AYUV, AV_PIX_FMT_NONE}, - { PJMEDIA_FORMAT_YUY2, AV_PIX_FMT_YUYV422}, -Index: pjmedia/src/pjmedia/format.c -=================================================================== ---- pjmedia/src/pjmedia/format.c (revision 5249) -+++ pjmedia/src/pjmedia/format.c (working copy) -@@ -74,6 +74,7 @@ - static pjmedia_video_format_info built_in_vid_fmt_info[] = - { - {PJMEDIA_FORMAT_RGB24, "RGB24", PJMEDIA_COLOR_MODEL_RGB, 24, 1, &apply_packed_fmt}, -+ {PJMEDIA_FORMAT_ARGB, "ARGB", PJMEDIA_COLOR_MODEL_RGB, 32, 1, &apply_packed_fmt}, - {PJMEDIA_FORMAT_RGBA, "RGBA", PJMEDIA_COLOR_MODEL_RGB, 32, 1, &apply_packed_fmt}, - {PJMEDIA_FORMAT_BGRA, "BGRA", PJMEDIA_COLOR_MODEL_RGB, 32, 1, &apply_packed_fmt}, - {PJMEDIA_FORMAT_DIB , "DIB ", PJMEDIA_COLOR_MODEL_RGB, 24, 1, &apply_packed_fmt}, -Index: pjmedia/src/pjmedia/rtcp.c -=================================================================== ---- pjmedia/src/pjmedia/rtcp.c (revision 5249) -+++ pjmedia/src/pjmedia/rtcp.c (working copy) -@@ -31,6 +31,7 @@ - #define RTCP_RR 201 - #define RTCP_SDES 202 - #define RTCP_BYE 203 -+#define RTCP_PSFB 206 /* Payload-specific FB message (RFC 4585) */ - #define RTCP_XR 207 - - enum { -@@ -764,6 +765,21 @@ - } - - -+static void parse_rtcp_psfb(pjmedia_rtcp_session *sess, -+ const void *pkt, -+ pj_size_t size) -+{ -+ pjmedia_rtcp_common *common = (pjmedia_rtcp_common*)pkt; -+ pj_assert(common->pt == RTCP_PSFB); -+ -+ if (common->count == 1) { -+ /* It's a PLI */ -+ PJ_LOG(5, (sess->name, "Received RTCP PLI")); -+ sess->keyframe_requested = PJ_TRUE; -+ } -+} -+ -+ - PJ_DEF(void) pjmedia_rtcp_rx_rtcp( pjmedia_rtcp_session *sess, - const void *pkt, - pj_size_t size) -@@ -770,6 +786,8 @@ - { - pj_uint8_t *p, *p_end; - -+ sess->keyframe_requested = PJ_FALSE; -+ - p = (pj_uint8_t*)pkt; - p_end = p + size; - while (p < p_end) { -@@ -789,6 +807,9 @@ - case RTCP_BYE: - parse_rtcp_bye(sess, p, len); - break; -+ case RTCP_PSFB: -+ parse_rtcp_psfb(sess, p, len); -+ break; - default: - /* Ignore unknown RTCP */ - TRACE_((sess->name, "Received unknown RTCP packet type=%d", -@@ -1072,6 +1093,34 @@ - } - - -+PJ_DEF(pj_status_t) pjmedia_rtcp_build_rtcp_pli(pjmedia_rtcp_session *session, -+ void *buf, -+ pj_size_t *length) -+{ -+ pjmedia_rtcp_common *hdr; -+ pj_uint8_t *p; -+ pj_size_t len = 12; /* pjmedia_rtcp_common + media SSRC (uint32_t) */ -+ -+ PJ_ASSERT_RETURN(session && buf && length, PJ_EINVAL); -+ -+ /* Verify buffer length */ -+ if (len > *length) -+ return PJ_ETOOSMALL; -+ -+ /* Build RTCP PLI */ -+ hdr = (pjmedia_rtcp_common*)buf; -+ pj_memcpy(hdr, &session->rtcp_sr_pkt.common, sizeof(*hdr)); -+ hdr->pt = RTCP_PSFB; -+ hdr->count = 1; /* FMT: 1 == Picture Loss Indication (PLI) */ -+ hdr->length = pj_htons((pj_uint16_t)(len/4 - 1)); -+ -+ p = (pj_uint8_t*)hdr + sizeof(*hdr); -+ pj_memset(p, 0, (pj_uint8_t*)hdr + len - p); -+ *length = len; -+ return PJ_SUCCESS; -+} -+ -+ - PJ_DEF(pj_status_t) pjmedia_rtcp_enable_xr( pjmedia_rtcp_session *sess, - pj_bool_t enable) - { -Index: pjmedia/src/pjmedia/sdp_neg.c -=================================================================== ---- pjmedia/src/pjmedia/sdp_neg.c (revision 5249) -+++ pjmedia/src/pjmedia/sdp_neg.c (working copy) -@@ -701,9 +701,8 @@ - return PJ_SUCCESS; - } - -- /* Process direction attributes */ -- update_media_direction(pool, answer, offer); -- -+ /* No need to update the direction when processing an answer */ -+ - /* If asymetric media is allowed, then just check that remote answer has - * codecs that are within the offer. - * -Index: pjmedia/src/pjmedia/sound_legacy.c -=================================================================== ---- pjmedia/src/pjmedia/sound_legacy.c (revision 5249) -+++ pjmedia/src/pjmedia/sound_legacy.c (working copy) -@@ -124,12 +124,6 @@ - pjmedia_aud_param param; - pj_status_t status; - -- /* Normalize rec_id & play_id */ -- if (dir & PJMEDIA_DIR_CAPTURE && rec_id < 0) -- rec_id = PJMEDIA_AUD_DEFAULT_CAPTURE_DEV; -- if (dir & PJMEDIA_DIR_PLAYBACK && play_id < 0) -- play_id = PJMEDIA_AUD_DEFAULT_PLAYBACK_DEV; -- - /* Initialize parameters */ - if (dir & PJMEDIA_DIR_CAPTURE) { - status = pjmedia_aud_dev_default_param(rec_id, ¶m); -Index: pjmedia/src/pjmedia/sound_port.c -=================================================================== ---- pjmedia/src/pjmedia/sound_port.c (revision 5249) -+++ pjmedia/src/pjmedia/sound_port.c (working copy) -@@ -98,7 +98,7 @@ - if (snd_port->ec_state) { - if (snd_port->ec_suspended) { - snd_port->ec_suspended = PJ_FALSE; -- //pjmedia_echo_state_reset(snd_port->ec_state); -+ pjmedia_echo_reset(snd_port->ec_state); - PJ_LOG(4,(THIS_FILE, "EC activated")); - } - snd_port->ec_suspend_count = 0; -@@ -311,15 +311,10 @@ - PJ_LOG(4,(THIS_FILE, "AEC tail is set to default %u ms", - snd_port->aud_param.ec_tail_ms)); - } -- -- status = pjmedia_snd_port_set_ec(snd_port, pool, -- snd_port->aud_param.ec_tail_ms, -- snd_port->prm_ec_options); -- if (status != PJ_SUCCESS) { -- pjmedia_aud_stream_destroy(snd_port->aud_stream); -- snd_port->aud_stream = NULL; -- return status; -- } -+ -+ pjmedia_snd_port_set_ec(snd_port, pool, -+ snd_port->aud_param.ec_tail_ms, -+ snd_port->prm_ec_options); - } - - /* Start sound stream. */ -@@ -550,6 +545,18 @@ - } - - -+/* Reset EC state */ -+PJ_DEF(pj_status_t) pjmedia_snd_port_reset_ec_state( pjmedia_snd_port *snd_port ) -+{ -+ PJ_ASSERT_RETURN(snd_port, PJ_EINVAL); -+ if (snd_port->ec_state) { -+ pjmedia_echo_reset(snd_port->ec_state); -+ PJ_LOG(4,(THIS_FILE, "EC reset")); -+ } -+ return PJ_SUCCESS; -+} -+ -+ - /* - * Change EC settings. - */ -Index: pjmedia/src/pjmedia/stream_info.c -=================================================================== ---- pjmedia/src/pjmedia/stream_info.c (revision 5249) -+++ pjmedia/src/pjmedia/stream_info.c (working copy) -@@ -33,47 +33,6 @@ - static const pj_str_t ID_RTPMAP = { "rtpmap", 6 }; - static const pj_str_t ID_TELEPHONE_EVENT = { "telephone-event", 15 }; - --static void get_opus_channels_and_clock_rate(const pjmedia_codec_fmtp *enc_fmtp, -- const pjmedia_codec_fmtp *dec_fmtp, -- unsigned *channel_cnt, -- unsigned *clock_rate) --{ -- unsigned i; -- unsigned enc_channel_cnt = 0, local_channel_cnt = 0; -- unsigned enc_clock_rate = 0, local_clock_rate = 0; -- -- for (i = 0; i < dec_fmtp->cnt; ++i) { -- if (!pj_stricmp2(&dec_fmtp->param[i].name, "sprop-maxcapturerate")) { -- local_clock_rate = (unsigned)pj_strtoul(&dec_fmtp->param[i].val); -- } else if (!pj_stricmp2(&dec_fmtp->param[i].name, "sprop-stereo")) { -- local_channel_cnt = (unsigned)pj_strtoul(&dec_fmtp->param[i].val); -- local_channel_cnt = (local_channel_cnt > 0) ? 2 : 1; -- } -- } -- if (!local_clock_rate) local_clock_rate = *clock_rate; -- if (!local_channel_cnt) local_channel_cnt = *channel_cnt; -- -- for (i = 0; i < enc_fmtp->cnt; ++i) { -- if (!pj_stricmp2(&enc_fmtp->param[i].name, "maxplaybackrate")) { -- enc_clock_rate = (unsigned)pj_strtoul(&enc_fmtp->param[i].val); -- } else if (!pj_stricmp2(&enc_fmtp->param[i].name, "stereo")) { -- enc_channel_cnt = (unsigned)pj_strtoul(&enc_fmtp->param[i].val); -- enc_channel_cnt = (enc_channel_cnt > 0) ? 2 : 1; -- } -- } -- /* The default is a standard mono session with 48000 Hz clock rate -- * (RFC 7587, section 7) -- */ -- if (!enc_clock_rate) enc_clock_rate = 48000; -- if (!enc_channel_cnt) enc_channel_cnt = 1; -- -- *clock_rate = (enc_clock_rate < local_clock_rate) ? enc_clock_rate : -- local_clock_rate; -- -- *channel_cnt = (enc_channel_cnt < local_channel_cnt) ? enc_channel_cnt : -- local_channel_cnt; --} -- - /* - * Internal function for collecting codec info and param from the SDP media. - */ -@@ -259,14 +218,6 @@ - pjmedia_stream_info_parse_fmtp(pool, local_m, si->rx_pt, - &si->param->setting.dec_fmtp); - -- if (!pj_stricmp2(&si->fmt.encoding_name, "opus")) { -- get_opus_channels_and_clock_rate(&si->param->setting.enc_fmtp, -- &si->param->setting.dec_fmtp, -- &si->fmt.channel_cnt, -- &si->fmt.clock_rate); -- } -- -- - /* Get the remote ptime for our encoder. */ - attr = pjmedia_sdp_attr_find2(rem_m->attr_count, rem_m->attr, - "ptime", NULL); -Index: pjmedia/src/pjmedia/transport_ice.c -=================================================================== ---- pjmedia/src/pjmedia/transport_ice.c (revision 5249) -+++ pjmedia/src/pjmedia/transport_ice.c (working copy) -@@ -147,6 +147,9 @@ - static void ice_on_ice_complete(pj_ice_strans *ice_st, - pj_ice_strans_op op, - pj_status_t status); -+static void ice_on_ice_state(pj_ice_strans *ice_st, -+ pj_ice_strans_state prev, -+ pj_ice_strans_state curr); - - - static pjmedia_transport_op transport_ice_op = -@@ -258,6 +261,7 @@ - /* Configure ICE callbacks */ - pj_bzero(&ice_st_cb, sizeof(ice_st_cb)); - ice_st_cb.on_ice_complete = &ice_on_ice_complete; -+ ice_st_cb.on_ice_state = &ice_on_ice_state; - ice_st_cb.on_rx_data = &ice_on_rx_data; - - /* Configure RTP socket buffer settings, if not set */ -@@ -283,6 +287,17 @@ - return PJ_SUCCESS; - } - -+/* -+ * Get the ICE stream transport associated with this media transport. -+ */ -+PJ_DEF(pj_ice_strans*) pjmedia_ice_get_strans(pjmedia_transport *tp) -+{ -+ struct transport_ice *tp_ice; -+ -+ tp_ice = (struct transport_ice*) tp; -+ return tp_ice->ice_st; -+} -+ - PJ_DEF(pj_grp_lock_t *) pjmedia_ice_get_grp_lock(pjmedia_transport *tp) - { - PJ_ASSERT_RETURN(tp, NULL); -@@ -303,6 +318,10 @@ - "Stopping ICE, reason=%s", reason)); - } - -+ /* Notify application about ICE stop */ -+ if (tp_ice->cb.on_ice_stop) -+ (*tp_ice->cb.on_ice_stop)(&tp_ice->base, (char *)reason, err); -+ - if (tp_ice->ice_st) { - pj_ice_strans_stop_ice(tp_ice->ice_st); - } -@@ -1526,7 +1545,7 @@ - if (status != PJ_SUCCESS) - return status; - -- pj_sockaddr_cp(&info->sock_info.rtp_addr_name, &cand.addr); -+ pj_sockaddr_cp(&info->sock_info.rtp_addr_name, &cand.base_addr); - - /* Get RTCP default address */ - if (tp_ice->comp_cnt > 1) { -@@ -1534,7 +1553,7 @@ - if (status != PJ_SUCCESS) - return status; - -- pj_sockaddr_cp(&info->sock_info.rtcp_addr_name, &cand.addr); -+ pj_sockaddr_cp(&info->sock_info.rtcp_addr_name, &cand.base_addr); - } - - /* Set remote address originating RTP & RTCP if this transport has -@@ -1833,6 +1852,20 @@ - } - - -+static void ice_on_ice_state(pj_ice_strans *ice_st, -+ pj_ice_strans_state prev, -+ pj_ice_strans_state curr) -+{ -+ struct transport_ice *tp_ice; -+ -+ tp_ice = (struct transport_ice*) pj_ice_strans_get_user_data(ice_st); -+ -+ /* Notify application */ -+ if (tp_ice->cb.on_ice_state) -+ (*tp_ice->cb.on_ice_state)(&tp_ice->base, prev, curr); -+} -+ -+ - /* Simulate lost */ - static pj_status_t transport_simulate_lost(pjmedia_transport *tp, - pjmedia_dir dir, -Index: pjmedia/src/pjmedia/transport_srtp.c -=================================================================== ---- pjmedia/src/pjmedia/transport_srtp.c (revision 5249) -+++ pjmedia/src/pjmedia/transport_srtp.c (working copy) -@@ -1337,6 +1337,10 @@ - pj_stricmp(&m_loc->desc.transport, &ID_RTP_SAVP) != 0) - goto BYPASS_SRTP; - -+ /* Do nothing if we are in bypass */ -+ if (srtp->bypass_srtp) -+ goto BYPASS_SRTP; -+ - /* If the media is inactive, do nothing. */ - /* No, we still need to process SRTP offer/answer even if the media is - * marked as inactive, because the transport is still alive in this -Index: pjmedia/src/pjmedia/vid_codec_util.c -=================================================================== ---- pjmedia/src/pjmedia/vid_codec_util.c (revision 5249) -+++ pjmedia/src/pjmedia/vid_codec_util.c (working copy) -@@ -515,15 +515,13 @@ - if (option & PJMEDIA_SDP_NEG_FMT_MATCH_ALLOW_MODIFY_ANSWER) { - unsigned i; - -- /* Flexible negotiation, if the answer has higher capability than -- * the offer, adjust the answer capability to be match to the offer. -+ /* Flexible negotiation, adjust our answer to the offer. -+ * Apply Postel's Principle (TM) in it's full glory. - */ -- if (a_fmtp.profile_idc >= o_fmtp.profile_idc) -+ if (a_fmtp.profile_idc != o_fmtp.profile_idc) - a_fmtp.profile_idc = o_fmtp.profile_idc; - if (a_fmtp.profile_iop != o_fmtp.profile_iop) - a_fmtp.profile_iop = o_fmtp.profile_iop; -- if (a_fmtp.level >= o_fmtp.level) -- a_fmtp.level = o_fmtp.level; - if (a_fmtp.packetization_mode >= o_fmtp.packetization_mode) - a_fmtp.packetization_mode = o_fmtp.packetization_mode; - -@@ -531,7 +529,6 @@ - #if H264_STRICT_SDP_NEGO - if (a_fmtp.profile_idc != o_fmtp.profile_idc || - a_fmtp.profile_iop != o_fmtp.profile_iop || -- a_fmtp.level != o_fmtp.level || - a_fmtp.packetization_mode != o_fmtp.packetization_mode) - { - return PJMEDIA_SDP_EFORMATNOTEQUAL; -@@ -551,8 +548,6 @@ - pj_val_to_hex_digit(a_fmtp.profile_idc, p); - p += 2; - pj_val_to_hex_digit(a_fmtp.profile_iop, p); -- p += 2; -- pj_val_to_hex_digit(a_fmtp.level, p); - } - else if (pj_stricmp(&a_fmtp_raw.param[i].name, &PACKETIZATION_MODE) == 0) - { -@@ -565,7 +560,6 @@ - /* Strict negotiation */ - if (a_fmtp.profile_idc != o_fmtp.profile_idc || - a_fmtp.profile_iop != o_fmtp.profile_iop || -- a_fmtp.level != o_fmtp.level || - a_fmtp.packetization_mode != o_fmtp.packetization_mode) - { - return PJMEDIA_SDP_EFORMATNOTEQUAL; -Index: pjmedia/src/pjmedia/vid_port.c -=================================================================== ---- pjmedia/src/pjmedia/vid_port.c (revision 5249) -+++ pjmedia/src/pjmedia/vid_port.c (working copy) -@@ -964,6 +964,7 @@ - PJ_LOG(3, (THIS_FILE, "reverting to its original format: %s", - status != PJMEDIA_EVID_ERR ? "success" : - "failure")); -+ pjmedia_vid_port_start(vp); - return status; - } - } -Index: pjmedia/src/pjmedia/vid_stream.c -=================================================================== ---- pjmedia/src/pjmedia/vid_stream.c (revision 5249) -+++ pjmedia/src/pjmedia/vid_stream.c (working copy) -@@ -142,10 +142,18 @@ - pjmedia_frame dec_frame; /**< Current decoded frame. */ - pjmedia_event fmt_event; /**< Buffered fmt_changed event - to avoid deadlock */ -+ pjmedia_event found_keyframe_event; -+ /**< Buffered found keyframe -+ event for delayed republish*/ -+ - pjmedia_event miss_keyframe_event; - /**< Buffered missing keyframe - event for delayed republish*/ - -+ pjmedia_event keyframe_req_event; -+ /**< Buffered keyframe request -+ event for delayed republish*/ -+ - unsigned frame_size; /**< Size of encoded base frame.*/ - unsigned frame_ts_len; /**< Frame length in timestamp. */ - -@@ -362,7 +370,7 @@ - const pjmedia_port_info *pi = &chan->port.info; - char fourcc_name[5]; - -- PJ_LOG(5, (pi->name.ptr, -+ PJ_LOG(4, (pi->name.ptr, - " %s format %s: %dx%d %s%s %d/%d(~%d)fps", - (chan->dir==PJMEDIA_DIR_DECODING? "Decoding":"Encoding"), - event_name, -@@ -392,6 +400,11 @@ - pj_memcpy(&stream->fmt_event, event, sizeof(*event)); - return PJ_SUCCESS; - -+ case PJMEDIA_EVENT_KEYFRAME_FOUND: -+ /* Republish this event later from get_frame(). */ -+ pj_memcpy(&stream->found_keyframe_event, event, sizeof(*event)); -+ return PJ_SUCCESS; -+ - case PJMEDIA_EVENT_KEYFRAME_MISSING: - /* Republish this event later from get_frame(). */ - pj_memcpy(&stream->miss_keyframe_event, event, sizeof(*event)); -@@ -797,6 +810,13 @@ - } - - pjmedia_rtcp_rx_rtcp(&stream->rtcp, pkt, bytes_read); -+ -+ /* XXX: posting some event from the RTCP session might be a better option */ -+ if (stream->rtcp.keyframe_requested) { -+ pjmedia_event event; -+ pjmedia_event_init(&event, PJMEDIA_EVENT_KEYFRAME_REQUESTED, NULL, stream); -+ pj_memcpy(&stream->keyframe_req_event, &event, sizeof(event)); -+ } - } - - static pj_status_t put_frame(pjmedia_port *port, -@@ -1137,12 +1157,13 @@ - stream->info.codec_param->dec_fmt.det.vid.fps = vfd->fps; - - /* Publish PJMEDIA_EVENT_FMT_CHANGED event if frame rate -- * increased and not exceeding 100fps. -+ * increased and not exceeding 60fps. - */ -- if (vfd->fps.num/vfd->fps.denum <= 100 && -+ if (vfd->fps.num/vfd->fps.denum <= 60.0 && - vfd->fps.num * stream->dec_max_fps.denum > - stream->dec_max_fps.num * vfd->fps.denum) - { -+ /*printf("FPS CHANGED: %d/%d -> %d/%d\n", stream->dec_max_fps.num, stream->dec_max_fps.denum, vfd->fps.num, vfd->fps.denum);*/ - pjmedia_event *event = &stream->fmt_event; - - /* Update max fps of decoding dir */ -@@ -1208,8 +1229,10 @@ - /* Override the framerate to be 1.5x higher in the event - * for the renderer. - */ -+#if 0 - fmt_chg_data->new_fmt.det.vid.fps.num *= 3; - fmt_chg_data->new_fmt.det.vid.fps.num /= 2; -+#endif - } else { - pjmedia_format_copy(&stream->info.codec_param->enc_fmt, - &fmt_chg_data->new_fmt); -@@ -1226,6 +1249,12 @@ - stream->fmt_event.type = PJMEDIA_EVENT_NONE; - } - -+ if (stream->found_keyframe_event.type != PJMEDIA_EVENT_NONE) { -+ pjmedia_event_publish(NULL, port, &stream->found_keyframe_event, -+ PJMEDIA_EVENT_PUBLISH_POST_EVENT); -+ stream->found_keyframe_event.type = PJMEDIA_EVENT_NONE; -+ } -+ - if (stream->miss_keyframe_event.type != PJMEDIA_EVENT_NONE) { - pjmedia_event_publish(NULL, port, &stream->miss_keyframe_event, - PJMEDIA_EVENT_PUBLISH_POST_EVENT); -@@ -1232,6 +1261,12 @@ - stream->miss_keyframe_event.type = PJMEDIA_EVENT_NONE; - } - -+ if (stream->keyframe_req_event.type != PJMEDIA_EVENT_NONE) { -+ pjmedia_event_publish(NULL, port, &stream->keyframe_req_event, -+ PJMEDIA_EVENT_PUBLISH_POST_EVENT); -+ stream->keyframe_req_event.type = PJMEDIA_EVENT_NONE; -+ } -+ - pj_mutex_lock( stream->jb_mutex ); - - if (stream->dec_frame.size == 0) { -@@ -1535,7 +1570,9 @@ - * local renderer clock) or video setup lag. Note that the actual framerate - * will be continuously calculated based on the incoming RTP timestamps. - */ -+#if 0 - vfd_dec->fps.num = vfd_dec->fps.num * 3 / 2; -+#endif - stream->dec_max_fps = vfd_dec->fps; - - /* Create decoder channel */ -@@ -1999,6 +2036,47 @@ - - - /* -+ * Send RTCP PLI. -+ */ -+PJ_DEF(pj_status_t) pjmedia_vid_stream_send_rtcp_pli( -+ pjmedia_vid_stream *stream) -+{ -+ PJ_ASSERT_RETURN(stream, PJ_EINVAL); -+ -+ if (stream->enc && stream->transport) { -+ void *sr_rr_pkt; -+ pj_uint8_t *pkt; -+ int len, max_len; -+ pj_status_t status; -+ pj_size_t pli_len; -+ -+ /* Build RTCP RR/SR packet */ -+ pjmedia_rtcp_build_rtcp(&stream->rtcp, &sr_rr_pkt, &len); -+ -+ pkt = (pj_uint8_t*) stream->out_rtcp_pkt; -+ pj_memcpy(pkt, sr_rr_pkt, len); -+ max_len = stream->out_rtcp_pkt_size; -+ -+ /* Build RTCP PLI packet */ -+ pli_len = max_len - len; -+ status = pjmedia_rtcp_build_rtcp_pli(&stream->rtcp, pkt+len, &pli_len); -+ if (status != PJ_SUCCESS) { -+ PJ_PERROR(4,(stream->name.ptr, status, "Error generating RTCP PLI")); -+ } else { -+ len += (int)pli_len; -+ } -+ -+ /* Send! */ -+ status = pjmedia_transport_send_rtcp(stream->transport, pkt, len); -+ -+ return status; -+ } -+ -+ return PJ_SUCCESS; -+} -+ -+ -+/* - * Initialize the video stream rate control with default settings. - */ - PJ_DEF(void) -Index: pjmedia/src/pjmedia/vid_tee.c -=================================================================== ---- pjmedia/src/pjmedia/vid_tee.c (revision 5249) -+++ pjmedia/src/pjmedia/vid_tee.c (working copy) -@@ -52,6 +52,7 @@ - unsigned dst_port_cnt; - vid_tee_dst_port *dst_ports; - pj_uint8_t *put_frm_flag; -+ pj_mutex_t *lock; - - struct vid_tee_conv_t { - pjmedia_converter *conv; -@@ -86,6 +87,11 @@ - tee->pf = pool->factory; - tee->pool = pj_pool_create(tee->pf, "video tee", 500, 500, NULL); - -+ /* Create lock */ -+ status = pj_mutex_create_simple(pool, "vid-tee-mutex", &tee->lock); -+ if (status != PJ_SUCCESS) -+ return status; -+ - /* Initialize video tee structure */ - tee->dst_port_maxcnt = max_dst_cnt; - tee->dst_ports = (vid_tee_dst_port*) -@@ -100,14 +106,16 @@ - - /* Initialize video tee buffer, its size is one frame */ - vfi = pjmedia_get_video_format_info(NULL, fmt->id); -- if (vfi == NULL) -- return PJMEDIA_EBADFMT; -+ if (vfi == NULL) { -+ status = PJMEDIA_EBADFMT; -+ goto on_error; -+ } - - pj_bzero(&vafp, sizeof(vafp)); - vafp.size = fmt->det.vid.size; - status = vfi->apply_fmt(vfi, &vafp); - if (status != PJ_SUCCESS) -- return status; -+ goto on_error; - - tee->buf_size = vafp.framebytes; - -@@ -118,7 +126,7 @@ - PJMEDIA_DIR_ENCODING, - fmt); - if (status != PJ_SUCCESS) -- return status; -+ goto on_error; - - tee->base.get_frame = &tee_get_frame; - tee->base.put_frame = &tee_put_frame; -@@ -128,6 +136,11 @@ - *p_vid_tee = &tee->base; - - return PJ_SUCCESS; -+ -+on_error: -+ pj_mutex_destroy(tee->lock); -+ tee->lock = NULL; -+ return status; - } - - static void realloc_buf(vid_tee_port *vid_tee, -@@ -169,23 +182,31 @@ - { - vid_tee_port *tee = (vid_tee_port*)vid_tee; - pjmedia_video_format_detail *vfd; -+ pj_status_t status; - - PJ_ASSERT_RETURN(vid_tee && vid_tee->info.signature==TEE_PORT_SIGN, - PJ_EINVAL); - -- if (tee->dst_port_cnt >= tee->dst_port_maxcnt) -- return PJ_ETOOMANY; -- -- if (vid_tee->info.fmt.id != port->info.fmt.id) -- return PJMEDIA_EBADFMT; -+ pj_mutex_lock(tee->lock); - -+ if (tee->dst_port_cnt >= tee->dst_port_maxcnt) { -+ status = PJ_ETOOMANY; -+ goto end; -+ } -+ -+ if (vid_tee->info.fmt.id != port->info.fmt.id) { -+ status = PJMEDIA_EBADFMT; -+ goto end; -+ } -+ - vfd = pjmedia_format_get_video_format_detail(&port->info.fmt, PJ_TRUE); - if (vfd->size.w != vid_tee->info.fmt.det.vid.size.w || - vfd->size.h != vid_tee->info.fmt.det.vid.size.h) - { -- return PJMEDIA_EBADFMT; -+ status = PJMEDIA_EBADFMT; -+ goto end; - } -- -+ - realloc_buf(tee, (option & PJMEDIA_VID_TEE_DST_DO_IN_PLACE_PROC)? - 1: 0, tee->buf_size); - -@@ -194,7 +215,11 @@ - tee->dst_ports[tee->dst_port_cnt].option = option; - ++tee->dst_port_cnt; - -- return PJ_SUCCESS; -+ status = PJ_SUCCESS; -+ -+end: -+ pj_mutex_unlock(tee->lock); -+ return status; - } - - -@@ -208,12 +233,17 @@ - { - vid_tee_port *tee = (vid_tee_port*)vid_tee; - pjmedia_video_format_detail *vfd; -+ pj_status_t status; - - PJ_ASSERT_RETURN(vid_tee && vid_tee->info.signature==TEE_PORT_SIGN, - PJ_EINVAL); - -- if (tee->dst_port_cnt >= tee->dst_port_maxcnt) -- return PJ_ETOOMANY; -+ pj_mutex_lock(tee->lock); -+ -+ if (tee->dst_port_cnt >= tee->dst_port_maxcnt) { -+ status = PJ_ETOOMANY; -+ goto end; -+ } - - pj_bzero(&tee->tee_conv[tee->dst_port_cnt], sizeof(tee->tee_conv[0])); - -@@ -226,17 +256,18 @@ - const pjmedia_video_format_info *vfi; - pjmedia_video_apply_fmt_param vafp; - pjmedia_conversion_param conv_param; -- pj_status_t status; - - vfi = pjmedia_get_video_format_info(NULL, port->info.fmt.id); -- if (vfi == NULL) -- return PJMEDIA_EBADFMT; -+ if (vfi == NULL) { -+ status = PJMEDIA_EBADFMT; -+ goto end; -+ } - - pj_bzero(&vafp, sizeof(vafp)); - vafp.size = port->info.fmt.det.vid.size; - status = vfi->apply_fmt(vfi, &vafp); - if (status != PJ_SUCCESS) -- return status; -+ goto end; - - realloc_buf(tee, (option & PJMEDIA_VID_TEE_DST_DO_IN_PLACE_PROC)? - 2: 1, vafp.framebytes); -@@ -248,7 +279,7 @@ - NULL, tee->pool, &conv_param, - &tee->tee_conv[tee->dst_port_cnt].conv); - if (status != PJ_SUCCESS) -- return status; -+ goto end; - - tee->tee_conv[tee->dst_port_cnt].conv_buf_size = vafp.framebytes; - } else { -@@ -259,8 +290,12 @@ - tee->dst_ports[tee->dst_port_cnt].dst = port; - tee->dst_ports[tee->dst_port_cnt].option = option; - ++tee->dst_port_cnt; -- -- return PJ_SUCCESS; -+ -+ status = PJ_SUCCESS; -+ -+end: -+ pj_mutex_unlock(tee->lock); -+ return status; - } - - -@@ -276,6 +311,8 @@ - PJ_ASSERT_RETURN(vid_tee && vid_tee->info.signature==TEE_PORT_SIGN, - PJ_EINVAL); - -+ pj_mutex_lock(tee->lock); -+ - for (i = 0; i < tee->dst_port_cnt; ++i) { - if (tee->dst_ports[i].dst == port) { - if (tee->tee_conv[i].conv) -@@ -286,10 +323,13 @@ - pj_array_erase(tee->tee_conv, sizeof(tee->tee_conv[0]), - tee->dst_port_cnt, i); - --tee->dst_port_cnt; -+ -+ pj_mutex_unlock(tee->lock); - return PJ_SUCCESS; - } - } - -+ pj_mutex_unlock(tee->lock); - return PJ_ENOTFOUND; - } - -@@ -300,6 +340,11 @@ - unsigned i, j; - const pj_uint8_t PUT_FRM_DONE = 1; - -+ if (pj_mutex_trylock(tee->lock) != PJ_SUCCESS) { -+ /* we are busy adding / removing consumers */ -+ return PJ_SUCCESS; -+ } -+ - pj_bzero(tee->put_frm_flag, tee->dst_port_cnt * - sizeof(tee->put_frm_flag[0])); - -@@ -364,6 +409,7 @@ - } - } - -+ pj_mutex_unlock(tee->lock); - return PJ_SUCCESS; - } - -@@ -383,6 +429,11 @@ - - PJ_ASSERT_RETURN(port && port->info.signature==TEE_PORT_SIGN, PJ_EINVAL); - -+ if (tee->lock) { -+ pj_mutex_destroy(tee->lock); -+ tee->lock = NULL; -+ } -+ - pj_pool_release(tee->pool); - if (tee->buf_pool) - pj_pool_release(tee->buf_pool); -Index: pjmedia/src/pjmedia-audiodev/alsa_dev.c -=================================================================== ---- pjmedia/src/pjmedia-audiodev/alsa_dev.c (revision 5249) -+++ pjmedia/src/pjmedia-audiodev/alsa_dev.c (working copy) -@@ -38,12 +38,7 @@ - - - #define THIS_FILE "alsa_dev.c" --#define ALSA_DEVICE_NAME "plughw:%d,%d" --#define ALSASOUND_PLAYBACK 1 --#define ALSASOUND_CAPTURE 2 --#define MAX_SOUND_CARDS 5 --#define MAX_SOUND_DEVICES_PER_CARD 5 --#define MAX_DEVICES 32 -+#define MAX_DEVICES 128 - - /* Set to 1 to enable tracing */ - #if 0 -@@ -71,7 +66,12 @@ - pjmedia_aud_play_cb play_cb, - void *user_data, - pjmedia_aud_stream **p_strm); -+static void alsa_factory_set_observer(pjmedia_aud_dev_factory *f, -+ pjmedia_aud_dev_change_callback cb); -+static int alsa_factory_get_default_rec_dev(pjmedia_aud_dev_factory *f); -+static int alsa_factory_get_default_play_dev(pjmedia_aud_dev_factory *f); - -+ - /* - * Stream prototypes - */ -@@ -88,6 +88,13 @@ - static pj_status_t alsa_stream_destroy(pjmedia_aud_stream *strm); - - -+/* alsa device info */ -+struct alsa_dev_info -+{ -+ pjmedia_aud_dev_info info; -+ char alsa_name[64]; -+}; -+ - struct alsa_factory - { - pjmedia_aud_dev_factory base; -@@ -96,7 +103,7 @@ - pj_pool_t *base_pool; - - unsigned dev_cnt; -- pjmedia_aud_dev_info devs[MAX_DEVICES]; -+ struct alsa_dev_info devs[MAX_DEVICES]; - }; - - struct alsa_stream -@@ -136,7 +143,10 @@ - &alsa_factory_get_dev_info, - &alsa_factory_default_param, - &alsa_factory_create_stream, -- &alsa_factory_refresh -+ &alsa_factory_refresh, -+ &alsa_factory_set_observer, -+ &alsa_factory_get_default_rec_dev, -+ &alsa_factory_get_default_play_dev - }; - - static pjmedia_aud_stream_op alsa_stream_op = -@@ -208,9 +218,9 @@ - } - - --static pj_status_t add_dev (struct alsa_factory *af, const char *dev_name) -+static pj_status_t add_dev (struct alsa_factory *af, const char *dev_name, const char *dev_desc) - { -- pjmedia_aud_dev_info *adi; -+ struct alsa_dev_info *adi; - snd_pcm_t* pcm; - int pb_result, ca_result; - -@@ -249,23 +259,63 @@ - pj_bzero(adi, sizeof(*adi)); - - /* Set device name */ -- strncpy(adi->name, dev_name, sizeof(adi->name)); -+ strncpy(adi->alsa_name, dev_name, sizeof(adi->alsa_name)); - -+ /* Set comprehensive device name */ -+ int name_size = sizeof(adi->info.name); -+ if (dev_desc) { -+ pj_bool_t name_set = PJ_FALSE; -+ if (strncmp("sysdefault", dev_name, 10) == 0) { -+ /* Only use first line for default device*/ -+ char *ptr = strstr(dev_desc, "\n"); -+ if (ptr) { -+ int len = ptr - dev_desc; -+ strncpy(adi->info.name, dev_desc, (len >= name_size-1)?name_size:len); -+ name_set = PJ_TRUE; -+ } -+ } else if (strncmp("iec958", dev_name, 6) == 0) { -+ /* Mangle name for SPDIF devices*/ -+ char *ptr = strstr(dev_desc, ","); -+ if (ptr) { -+ int len = ptr - dev_desc; -+ if (len + 18 < name_size) { -+ strncpy(adi->info.name, dev_desc, len); -+ strncpy(adi->info.name+len, ", Digital (S/PDIF)", 18); -+ name_set = PJ_TRUE; -+ } -+ } -+ } -+ -+ if (!name_set) { -+ /* Use the entire description for other device names */ -+ int i = 0; -+ while (i < name_size-1 && dev_desc[i] != '\0') { -+ if (dev_desc[i] == '\n' || dev_desc[i] == '\r') -+ adi->info.name[i] = ' '; -+ else -+ adi->info.name[i] = dev_desc[i]; -+ i++; -+ } -+ } -+ } else { -+ strncpy(adi->info.name, dev_name, name_size); -+ } -+ - /* Check the number of playback channels */ -- adi->output_count = (pb_result>=0) ? 1 : 0; -+ adi->info.output_count = (pb_result>=0) ? 1 : 0; - - /* Check the number of capture channels */ -- adi->input_count = (ca_result>=0) ? 1 : 0; -+ adi->info.input_count = (ca_result>=0) ? 1 : 0; - - /* Set the default sample rate */ -- adi->default_samples_per_sec = 8000; -+ adi->info.default_samples_per_sec = 8000; - - /* Driver name */ -- strcpy(adi->driver, "ALSA"); -+ strcpy(adi->info.driver, "ALSA"); - - ++af->dev_cnt; - -- PJ_LOG (5,(THIS_FILE, "Added sound device %s", adi->name)); -+ PJ_LOG (5,(THIS_FILE, "Added sound device %s", adi->alsa_name)); - - return PJ_SUCCESS; - } -@@ -348,9 +398,26 @@ - n = hints; - while (*n != NULL) { - char *name = snd_device_name_get_hint(*n, "NAME"); -- if (name != NULL && 0 != strcmp("null", name)) { -- add_dev(af, name); -+ char *desc = snd_device_name_get_hint(*n, "DESC"); -+ if (name != NULL) { -+ if (strncmp("null", name, 4) == 0 || -+ strncmp("front", name, 5) == 0 || -+ strncmp("rear", name, 4) == 0 || -+ strncmp("side", name, 4) == 0 || -+ strncmp("dmix", name, 4) == 0 || -+ strncmp("dsnoop", name, 6) == 0 || -+ strncmp("hw", name, 2) == 0 || -+ strncmp("plughw", name, 6) == 0 || -+ strncmp("center_lfe", name, 10) == 0 || -+ strncmp("surround", name, 8) == 0 || -+ (strncmp("default", name, 7) == 0 && strstr(name, ":CARD=") != NULL)) { -+ /* skip these devices, 'sysdefault' always contains the relevant information */ -+ ; -+ } else { -+ add_dev(af, name, desc); -+ } - free(name); -+ free(desc); - } - n++; - } -@@ -385,7 +452,7 @@ - - PJ_ASSERT_RETURN(index>=0 && indexdev_cnt, PJ_EINVAL); - -- pj_memcpy(info, &af->devs[index], sizeof(*info)); -+ pj_memcpy(info, &af->devs[index].info, sizeof(*info)); - info->caps = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY | - PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY; - return PJ_SUCCESS; -@@ -397,7 +464,7 @@ - pjmedia_aud_param *param) - { - struct alsa_factory *af = (struct alsa_factory*)f; -- pjmedia_aud_dev_info *adi; -+ struct alsa_dev_info *adi; - - PJ_ASSERT_RETURN(index>=0 && indexdev_cnt, PJ_EINVAL); - -@@ -404,15 +471,15 @@ - adi = &af->devs[index]; - - pj_bzero(param, sizeof(*param)); -- if (adi->input_count && adi->output_count) { -+ if (adi->info.input_count && adi->info.output_count) { - param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK; - param->rec_id = index; - param->play_id = index; -- } else if (adi->input_count) { -+ } else if (adi->info.input_count) { - param->dir = PJMEDIA_DIR_CAPTURE; - param->rec_id = index; - param->play_id = PJMEDIA_AUD_INVALID_DEV; -- } else if (adi->output_count) { -+ } else if (adi->info.output_count) { - param->dir = PJMEDIA_DIR_PLAYBACK; - param->play_id = index; - param->rec_id = PJMEDIA_AUD_INVALID_DEV; -@@ -420,11 +487,11 @@ - return PJMEDIA_EAUD_INVDEV; - } - -- param->clock_rate = adi->default_samples_per_sec; -+ param->clock_rate = adi->info.default_samples_per_sec; - param->channel_count = 1; -- param->samples_per_frame = adi->default_samples_per_sec * 20 / 1000; -+ param->samples_per_frame = adi->info.default_samples_per_sec * 20 / 1000; - param->bits_per_sample = 16; -- param->flags = adi->caps; -+ param->flags = adi->info.caps; - param->input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY; - param->output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY; - -@@ -549,7 +616,7 @@ - - tstamp.u64 += nframes; - } -- snd_pcm_drain (pcm); -+ snd_pcm_drop (pcm); - TRACE_((THIS_FILE, "ca_thread_func: Stopped")); - - return PJ_SUCCESS; -@@ -571,9 +638,9 @@ - - /* Open PCM for playback */ - PJ_LOG (5,(THIS_FILE, "open_playback: Open playback device '%s'", -- stream->af->devs[param->play_id].name)); -+ stream->af->devs[param->play_id].alsa_name)); - result = snd_pcm_open (&stream->pb_pcm, -- stream->af->devs[param->play_id].name, -+ stream->af->devs[param->play_id].alsa_name, - SND_PCM_STREAM_PLAYBACK, - 0); - if (result < 0) -@@ -665,7 +732,7 @@ - - PJ_LOG (5,(THIS_FILE, "Opened device alsa(%s) for playing, sample rate=%d" - ", ch=%d, bits=%d, period size=%d frames, latency=%d ms", -- stream->af->devs[param->play_id].name, -+ stream->af->devs[param->play_id].alsa_name, - rate, param->channel_count, - param->bits_per_sample, stream->pb_frames, - (int)stream->param.output_latency_ms)); -@@ -689,9 +756,9 @@ - - /* Open PCM for capture */ - PJ_LOG (5,(THIS_FILE, "open_capture: Open capture device '%s'", -- stream->af->devs[param->rec_id].name)); -+ stream->af->devs[param->rec_id].alsa_name)); - result = snd_pcm_open (&stream->ca_pcm, -- stream->af->devs[param->rec_id].name, -+ stream->af->devs[param->rec_id].alsa_name, - SND_PCM_STREAM_CAPTURE, - 0); - if (result < 0) -@@ -783,7 +850,7 @@ - - PJ_LOG (5,(THIS_FILE, "Opened device alsa(%s) for capture, sample rate=%d" - ", ch=%d, bits=%d, period size=%d frames, latency=%d ms", -- stream->af->devs[param->rec_id].name, -+ stream->af->devs[param->rec_id].alsa_name, - rate, param->channel_count, - param->bits_per_sample, stream->ca_frames, - (int)stream->param.input_latency_ms)); -@@ -845,6 +912,28 @@ - } - - -+/* API: set audio device change observer */ -+static void alsa_factory_set_observer(pjmedia_aud_dev_factory *f, -+ pjmedia_aud_dev_change_callback cb) -+{ -+ PJ_UNUSED_ARG(f); -+ PJ_UNUSED_ARG(cb); -+} -+ -+/* API: get default recording device */ -+static int alsa_factory_get_default_rec_dev(pjmedia_aud_dev_factory *f) -+{ -+ PJ_UNUSED_ARG(f); -+ return -1; -+} -+ -+/* API: get default playback device */ -+static int alsa_factory_get_default_play_dev(pjmedia_aud_dev_factory *f) -+{ -+ PJ_UNUSED_ARG(f); -+ return -1; -+} -+ - /* API: get running parameter */ - static pj_status_t alsa_stream_get_param(pjmedia_aud_stream *s, - pjmedia_aud_param *pi) -Index: pjmedia/src/pjmedia-audiodev/audiodev.c -=================================================================== ---- pjmedia/src/pjmedia-audiodev/audiodev.c (revision 5249) -+++ pjmedia/src/pjmedia-audiodev/audiodev.c (working copy) -@@ -65,11 +65,6 @@ - #define DEFAULT_DEV_ID 0 - - --/* extern functions to create factories */ --#if PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO --pjmedia_aud_dev_factory* pjmedia_pa_factory(pj_pool_factory *pf); --#endif -- - #if PJMEDIA_AUDIO_DEV_HAS_COREAUDIO - pjmedia_aud_dev_factory* pjmedia_coreaudio_factory(pj_pool_factory *pf); - #endif -@@ -130,7 +125,6 @@ - unsigned start_idx; /* Start index in global list */ - int rec_dev_idx;/* Default capture device. */ - int play_dev_idx;/* Default playback device */ -- int dev_idx; /* Default device. */ - }; - - /* The audio subsystem */ -@@ -145,8 +139,64 @@ - unsigned dev_cnt; /* Total number of devices. */ - pj_uint32_t dev_list[MAX_DEVS];/* Array of device IDs. */ - -+ pjmedia_aud_dev_observer dev_observer; -+ - } aud_subsys; - -+/* callback for device change operations */ -+static void process_aud_dev_change_event(pjmedia_aud_dev_change_event event) -+{ -+ pj_status_t status; -+ -+ if (!pj_thread_is_registered()) { -+ status = pj_thread_register("aud_dev_observer", aud_subsys.dev_observer.thread_desc, &aud_subsys.dev_observer.thread); -+ if (status != PJ_SUCCESS) { -+ return; -+ } -+ PJ_LOG(5, (THIS_FILE, "Audio device change thread registered")); -+ } -+ -+ status = pj_mutex_lock(aud_subsys.dev_observer.lock); -+ if (status != PJ_SUCCESS) { -+ PJ_LOG(5, (THIS_FILE, "Could not acquire audio device change lock")); -+ return; -+ } -+ -+ if (!aud_subsys.dev_observer.cb) { -+ /* there is no registered callback to call */ -+ goto end; -+ } -+ -+ switch(event) { -+ case DEFAULT_INPUT_CHANGED: -+ PJ_LOG(5, (THIS_FILE, "Default input device changed")); -+ pjmedia_aud_dev_refresh(); -+ (*aud_subsys.dev_observer.cb)(PJMEDIA_AUD_DEV_DEFAULT_INPUT_CHANGED); -+ break; -+ case DEFAULT_OUTPUT_CHANGED: -+ PJ_LOG(5, (THIS_FILE, "Default output device changed")); -+ pjmedia_aud_dev_refresh(); -+ (*aud_subsys.dev_observer.cb)(PJMEDIA_AUD_DEV_DEFAULT_OUTPUT_CHANGED); -+ break; -+ case DEVICE_LIST_CHANGED: -+ PJ_LOG(5, (THIS_FILE, "Device list changed")); -+ (*aud_subsys.dev_observer.cb)(PJMEDIA_AUD_DEV_LIST_WILL_REFRESH); -+ pjmedia_aud_dev_refresh(); -+ (*aud_subsys.dev_observer.cb)(PJMEDIA_AUD_DEV_LIST_DID_REFRESH); -+ break; -+ default: -+ PJ_LOG(5, (THIS_FILE, "Unknown event: %d", event)); -+ break; -+ } -+ -+end: -+ status = pj_mutex_unlock(aud_subsys.dev_observer.lock); -+ if (status != PJ_SUCCESS) { -+ PJ_LOG(5, (THIS_FILE, "Could not release audio device change lock")); -+ } -+ -+} -+ - /* API: get capability name/info */ - PJ_DEF(const char*) pjmedia_aud_dev_cap_name(pjmedia_aud_dev_cap cap, - const char **p_desc) -@@ -291,6 +341,11 @@ - f = drv->f; - } - -+ /* Register device change observer */ -+ if (!refresh) { -+ f->op->set_dev_change_cb(f, &process_aud_dev_change_event); -+ } -+ - if (!f) - return PJ_EUNKNOWN; - -@@ -314,8 +369,8 @@ - */ - - /* Fill in default devices */ -- drv->play_dev_idx = drv->rec_dev_idx = -- drv->dev_idx = PJMEDIA_AUD_INVALID_DEV; -+ drv->rec_dev_idx = f->op->get_default_rec_dev(f); -+ drv->play_dev_idx = f->op->get_default_play_dev(f); - for (i=0; irec_dev_idx = i; - } -- if (drv->dev_idx < 0 && info.input_count && -- info.output_count) -- { -- /* Set default capture and playback device */ -- drv->dev_idx = i; -- } - -- if (drv->play_dev_idx >= 0 && drv->rec_dev_idx >= 0 && -- drv->dev_idx >= 0) -+ if (drv->play_dev_idx >= 0 && drv->rec_dev_idx >= 0) - { - /* Done. */ - break; -@@ -374,13 +422,13 @@ - struct driver *drv = &aud_subsys.drv[drv_idx]; - - if (drv->f) { -+ drv->f->op->set_dev_change_cb(drv->f, NULL); - drv->f->op->destroy(drv->f); - drv->f = NULL; - } - - pj_bzero(drv, sizeof(*drv)); -- drv->play_dev_idx = drv->rec_dev_idx = -- drv->dev_idx = PJMEDIA_AUD_INVALID_DEV; -+ drv->play_dev_idx = drv->rec_dev_idx = PJMEDIA_AUD_INVALID_DEV; - } - - /* API: Initialize the audio subsystem. */ -@@ -423,9 +471,6 @@ - #if PJMEDIA_AUDIO_DEV_HAS_COREAUDIO - aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_coreaudio_factory; - #endif --#if PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO -- aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_pa_factory; --#endif - #if PJMEDIA_AUDIO_DEV_HAS_WMME - aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_wmme_factory; - #endif -@@ -445,6 +490,18 @@ - aud_subsys.drv[aud_subsys.drv_cnt++].create = &pjmedia_null_audio_factory; - #endif - -+ /* Initialize audio device observer objects */ -+ pj_status_t st; -+ aud_subsys.dev_observer.pool = pj_pool_create(pf, "aud_dev_observer_pool", 512, 512, NULL); -+ if (!aud_subsys.dev_observer.pool) { -+ return PJ_ENOMEM; -+ } -+ st = pj_mutex_create_simple(aud_subsys.dev_observer.pool, "aud_dev_observer_lock", &aud_subsys.dev_observer.lock); -+ if (st != PJ_SUCCESS) { -+ return st; -+ } -+ aud_subsys.dev_observer.cb = NULL; -+ - /* Initialize each factory and build the device ID list */ - for (i=0; idev_idx >= 0) { -- id = drv->dev_idx; -- make_global_index(i, &id); -- break; -- } else if (id==PJMEDIA_AUD_DEFAULT_CAPTURE_DEV && -+ if (id==PJMEDIA_AUD_DEFAULT_CAPTURE_DEV && - drv->rec_dev_idx >= 0) - { - id = drv->rec_dev_idx; -@@ -609,7 +665,7 @@ - id = drv->play_dev_idx; - make_global_index(i, &id); - break; -- } -+ } - } - - if (id < 0) { -@@ -844,4 +900,24 @@ - return strm->op->destroy(strm); - } - -+/* API: Register device change observer. */ -+PJ_DEF(pj_status_t) pjmedia_aud_dev_set_observer_cb(pjmedia_aud_dev_observer_callback cb) -+{ -+ pj_status_t status; - -+ status = pj_mutex_lock(aud_subsys.dev_observer.lock); -+ if (status != PJ_SUCCESS) { -+ PJ_LOG(5, (THIS_FILE, "Could not acquire audio device change lock")); -+ return status; -+ } -+ -+ aud_subsys.dev_observer.cb = cb; -+ -+ status = pj_mutex_unlock(aud_subsys.dev_observer.lock); -+ if (status != PJ_SUCCESS) { -+ PJ_LOG(5, (THIS_FILE, "Could not release audio device change lock")); -+ } -+ -+ return status; -+} -+ -Index: pjmedia/src/pjmedia-audiodev/coreaudio_dev.m -=================================================================== ---- pjmedia/src/pjmedia-audiodev/coreaudio_dev.m (revision 5249) -+++ pjmedia/src/pjmedia-audiodev/coreaudio_dev.m (working copy) -@@ -166,6 +166,11 @@ - void *user_data, - pjmedia_aud_stream **p_aud_strm); - -+static void ca_factory_set_observer(pjmedia_aud_dev_factory *f, -+ pjmedia_aud_dev_change_callback cb); -+static int ca_factory_get_default_rec_dev(pjmedia_aud_dev_factory *f); -+static int ca_factory_get_default_play_dev(pjmedia_aud_dev_factory *f); -+ - static pj_status_t ca_stream_get_param(pjmedia_aud_stream *strm, - pjmedia_aud_param *param); - static pj_status_t ca_stream_get_cap(pjmedia_aud_stream *strm, -@@ -199,7 +204,10 @@ - &ca_factory_get_dev_info, - &ca_factory_default_param, - &ca_factory_create_stream, -- &ca_factory_refresh -+ &ca_factory_refresh, -+ &ca_factory_set_observer, -+ &ca_factory_get_default_rec_dev, -+ &ca_factory_get_default_play_dev - }; - - static pjmedia_aud_stream_op stream_op = -@@ -701,6 +709,169 @@ - return PJ_SUCCESS; - } - -+static OSStatus property_listener_proc(AudioObjectID objectID, -+ UInt32 numberAddresses, -+ const AudioObjectPropertyAddress inAddresses[], -+ void *clientData) -+{ -+ pjmedia_aud_dev_change_callback cb = (pjmedia_aud_dev_change_callback)clientData; -+ pjmedia_aud_dev_change_event event; -+ UInt32 i; -+ -+ for(i = 0; i < numberAddresses; i++) { -+ event = 0; -+ switch (inAddresses[i].mSelector) { -+ case kAudioHardwarePropertyDefaultInputDevice: -+ event = DEFAULT_INPUT_CHANGED; -+ break; -+ case kAudioHardwarePropertyDefaultOutputDevice: -+ event = DEFAULT_OUTPUT_CHANGED; -+ break; -+ case kAudioHardwarePropertyDevices: -+ event = DEVICE_LIST_CHANGED; -+ break; -+ default: -+ break; -+ } -+ if (event > 0) { -+ (cb)(event); -+ } -+ } -+ -+ return noErr; -+} -+ -+/* API: set audio device change observer */ -+static void ca_factory_set_observer(pjmedia_aud_dev_factory *f, -+ pjmedia_aud_dev_change_callback cb) -+{ -+ AudioObjectPropertyAddress addr; -+ OSStatus ostatus; -+ -+ /* observer for devices list */ -+ addr.mSelector = kAudioHardwarePropertyDevices; -+ addr.mScope = kAudioObjectPropertyScopeGlobal; -+ addr.mElement = kAudioObjectPropertyElementMaster; -+ -+ if (cb) { -+ ostatus = AudioObjectAddPropertyListener(kAudioObjectSystemObject, -+ &addr, -+ property_listener_proc, -+ cb); -+ } else { -+ ostatus = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, -+ &addr, -+ property_listener_proc, -+ cb); -+ } -+ if (ostatus != noErr) { -+ PJ_LOG(5,(THIS_FILE, "Error %sregistering devices list observer", cb==NULL ? "un-" : "")); -+ } -+ -+ /* observer for default input device */ -+ addr.mSelector = kAudioHardwarePropertyDefaultInputDevice; -+ -+ if (cb) { -+ ostatus = AudioObjectAddPropertyListener(kAudioObjectSystemObject, -+ &addr, -+ property_listener_proc, -+ cb); -+ } else { -+ ostatus = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, -+ &addr, -+ property_listener_proc, -+ cb); -+ } -+ if (ostatus != noErr) { -+ PJ_LOG(5,(THIS_FILE, "Error %sregistering default input device observer", cb==NULL ? "un-" : "")); -+ } -+ -+ /* observer for default output device */ -+ addr.mSelector = kAudioHardwarePropertyDefaultOutputDevice; -+ -+ if (cb) { -+ ostatus = AudioObjectAddPropertyListener(kAudioObjectSystemObject, -+ &addr, -+ property_listener_proc, -+ cb); -+ } else { -+ ostatus = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, -+ &addr, -+ property_listener_proc, -+ cb); -+ } -+ if (ostatus != noErr) { -+ PJ_LOG(5,(THIS_FILE, "Error %sregistering default output device observer", cb==NULL ? "un-" : "")); -+ } -+ -+} -+ -+/* API: get default recording device */ -+static int ca_factory_get_default_rec_dev(pjmedia_aud_dev_factory *f) -+{ -+ AudioDeviceID dev_id = kAudioObjectUnknown; -+ AudioObjectPropertyAddress addr; -+ UInt32 size; -+ OSStatus ostatus; -+ int i; -+ int idx = -1; -+ struct coreaudio_factory *cf = (struct coreaudio_factory*)f; -+ -+ /* Find default audio input device */ -+ addr.mSelector = kAudioHardwarePropertyDefaultInputDevice; -+ addr.mScope = kAudioObjectPropertyScopeGlobal; -+ addr.mElement = kAudioObjectPropertyElementMaster; -+ size = sizeof(dev_id); -+ -+ ostatus = AudioObjectGetPropertyData(kAudioObjectSystemObject, -+ &addr, 0, NULL, -+ &size, (void *)&dev_id); -+ if (ostatus == noErr) { -+ for (i = 0; i < cf->dev_count; i++) { -+ struct coreaudio_dev_info *cdi; -+ cdi = &cf->dev_info[i]; -+ if (cdi->dev_id == dev_id) { -+ idx = i; -+ break; -+ } -+ } -+ } -+ return idx; -+} -+ -+/* API: get default playback device */ -+static int ca_factory_get_default_play_dev(pjmedia_aud_dev_factory *f) -+{ -+ AudioDeviceID dev_id = kAudioObjectUnknown; -+ AudioObjectPropertyAddress addr; -+ UInt32 size; -+ OSStatus ostatus; -+ int i; -+ int idx = -1; -+ struct coreaudio_factory *cf = (struct coreaudio_factory*)f; -+ -+ /* Find default audio output device */ -+ addr.mSelector = kAudioHardwarePropertyDefaultOutputDevice; -+ addr.mScope = kAudioObjectPropertyScopeGlobal; -+ addr.mElement = kAudioObjectPropertyElementMaster; -+ size = sizeof(dev_id); -+ -+ ostatus = AudioObjectGetPropertyData(kAudioObjectSystemObject, -+ &addr, 0, NULL, -+ &size, (void *)&dev_id); -+ if (ostatus == noErr) { -+ for (i = 0; i < cf->dev_count; i++) { -+ struct coreaudio_dev_info *cdi; -+ cdi = &cf->dev_info[i]; -+ if (cdi->dev_id == dev_id) { -+ idx = i; -+ break; -+ } -+ } -+ } -+ return idx; -+} -+ - OSStatus resampleProc(AudioConverterRef inAudioConverter, - UInt32 *ioNumberDataPackets, - AudioBufferList *ioData, -@@ -1727,7 +1898,6 @@ - { - strm->param.input_latency_ms = (latency + latency2) * 1000 / - strm->param.clock_rate; -- strm->param.input_latency_ms++; - } - } - #else -@@ -1735,7 +1905,6 @@ - strm->param.input_latency_ms = - (unsigned)(([strm->sess inputLatency] + - [strm->sess IOBufferDuration]) * 1000); -- strm->param.input_latency_ms++; - } else - return PJMEDIA_EAUD_INVCAP; - #endif -@@ -1768,7 +1937,6 @@ - { - strm->param.output_latency_ms = (latency + latency2) * 1000 / - strm->param.clock_rate; -- strm->param.output_latency_ms++; - } - } - #else -@@ -1776,11 +1944,10 @@ - strm->param.output_latency_ms = - (unsigned)(([strm->sess outputLatency] + - [strm->sess IOBufferDuration]) * 1000); -- strm->param.output_latency_ms++; - } else - return PJMEDIA_EAUD_INVCAP; - #endif -- *(unsigned*)pval = (++strm->param.output_latency_ms * 2); -+ *(unsigned*)pval = strm->param.output_latency_ms; - return PJ_SUCCESS; - } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING && - (strm->param.dir & PJMEDIA_DIR_PLAYBACK)) -Index: pjmedia/src/pjmedia-audiodev/errno.c -=================================================================== ---- pjmedia/src/pjmedia-audiodev/errno.c (revision 5249) -+++ pjmedia/src/pjmedia-audiodev/errno.c (working copy) -@@ -19,9 +19,6 @@ - #include - #include - #include --#if PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO --# include --#endif - #if PJMEDIA_AUDIO_DEV_HAS_WMME - # ifdef _MSC_VER - # pragma warning(push, 3) -@@ -91,26 +88,6 @@ - } else - #endif - -- /* See if the error comes from PortAudio. */ --#if PJMEDIA_AUDIO_DEV_HAS_PORTAUDIO -- if (statcode >= PJMEDIA_AUDIODEV_PORTAUDIO_ERRNO_START && -- statcode <= PJMEDIA_AUDIODEV_PORTAUDIO_ERRNO_END) -- { -- -- //int pa_err = statcode - PJMEDIA_ERRNO_FROM_PORTAUDIO(0); -- int pa_err = PJMEDIA_AUDIODEV_PORTAUDIO_ERRNO_START - statcode; -- pj_str_t msg; -- -- msg.ptr = (char*)Pa_GetErrorText(pa_err); -- msg.slen = pj_ansi_strlen(msg.ptr); -- -- errstr.ptr = buf; -- pj_strncpy_with_null(&errstr, &msg, bufsize); -- return errstr; -- -- } else --#endif /* PJMEDIA_SOUND_IMPLEMENTATION */ -- - /* See if the error comes from WMME */ - #if PJMEDIA_AUDIO_DEV_HAS_WMME - if ((statcode >= PJMEDIA_AUDIODEV_WMME_IN_ERROR_START && -Index: pjmedia/src/pjmedia-audiodev/wmme_dev.c -=================================================================== ---- pjmedia/src/pjmedia-audiodev/wmme_dev.c (revision 5249) -+++ pjmedia/src/pjmedia-audiodev/wmme_dev.c (working copy) -@@ -31,6 +31,7 @@ - #endif - - #include -+#include - #include - #include - -@@ -68,6 +69,15 @@ - - #define THIS_FILE "wmme_dev.c" - -+/* WMME device change observer */ -+struct wmme_dev_observer -+{ -+ pj_thread_t *thread; -+ pj_pool_t *pool; -+ pjmedia_aud_dev_change_callback cb; -+ HWND hWnd; -+}; -+ - /* WMME device info */ - struct wmme_dev_info - { -@@ -86,6 +96,8 @@ - - unsigned dev_count; - struct wmme_dev_info *dev_info; -+ -+ struct wmme_dev_observer dev_observer; - }; - - -@@ -150,7 +162,12 @@ - pjmedia_aud_play_cb play_cb, - void *user_data, - pjmedia_aud_stream **p_aud_strm); -+static void factory_set_observer(pjmedia_aud_dev_factory *f, -+ pjmedia_aud_dev_change_callback cb); -+static int factory_get_default_rec_dev(pjmedia_aud_dev_factory *f); -+static int factory_get_default_play_dev(pjmedia_aud_dev_factory *f); - -+ - static pj_status_t stream_get_param(pjmedia_aud_stream *strm, - pjmedia_aud_param *param); - static pj_status_t stream_get_cap(pjmedia_aud_stream *strm, -@@ -173,7 +190,10 @@ - &factory_get_dev_info, - &factory_default_param, - &factory_create_stream, -- &factory_refresh -+ &factory_refresh, -+ &factory_set_observer, -+ &factory_get_default_rec_dev, -+ &factory_get_default_play_dev - }; - - static pjmedia_aud_stream_op stream_op = -@@ -1306,6 +1326,201 @@ - return PJ_SUCCESS; - } - -+/* Processes OS messages arriving at the hWnd window */ -+INT_PTR WINAPI ProcessOSMessage(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) -+{ -+ /* wf is used in order to query the number of audio devices currently handled */ -+ static struct wmme_factory *wf = NULL; -+ -+ switch( message ) -+ { -+ case WM_CREATE: -+ /* Initialize wf pointer on the first run */ -+ if (wf == NULL) -+ { -+ CREATESTRUCT *CrtStrPtr = (CREATESTRUCT *) lParam; -+ wf = (struct wmme_factory *)(CrtStrPtr->lpCreateParams); -+ } -+ break; -+ case WM_DEVICECHANGE: -+ /* Possible insertion or removal of device. There's some issues: -+ -+ - Some devices/drivers does not trigger arrival nor -+ removecomplete events, but only devnodes_changed events. -+ Therefore, we process all of those type of events. -+ -+ - Some hardware can send many devnodes_changed events at the -+ same time (up to ~15 of such events). These batches are -+ detected using temporal locality, using constMaxBatchPeriod_. -+ Once the device is detected, the rest of redundant events -+ are discarded. In order to know if there's a new device or not, -+ actual audio devices count is compared to stored audio devices -+ count (via wf->dev_count). -+ -+ - Hardware takes some time to settle and be recognized by -+ drivers. A small window of time is given in order to account -+ for this (constMaxSettleTime_); -+ -+ Settle time should be slightly lower than batch period. -+ */ -+ if (wParam == DBT_DEVICEARRIVAL || wParam == DBT_DEVICEREMOVECOMPLETE || wParam == DBT_DEVNODES_CHANGED) { -+ const int constMaxBatchPeriod_ = 3; /* seconds */ -+ const int constMaxSettleTime_ = (constMaxBatchPeriod_ * 1000) - 500; /* milliseconds */ -+ -+ /* Loop that allows hardware to settle */ -+ int settleTimeLeft = constMaxSettleTime_; -+ while (settleTimeLeft > 0) { -+ /* Check if actual devices lists (I/O) sizes have actually -+ changed before notifying upper levels. Consider input -+ devices, output devices and a WAVE MAPPER device for each. -+ */ -+ if(waveInGetNumDevs() + waveOutGetNumDevs() + 2 != wf->dev_count) { -+ /* Hardware changed */ -+ if (wf->dev_observer.cb) { -+ wf->dev_observer.cb(DEVICE_LIST_CHANGED); -+ } -+ break; -+ } else { -+ /* Hardware is settling... */ -+ Sleep(250); -+ settleTimeLeft -= 250; -+ } -+ } -+ } -+ break; -+ case WM_CLOSE: -+ if (!DestroyWindow(hWnd)) { -+ PJ_LOG(4,(THIS_FILE, "Couldn't destroy message window")); -+ } -+ break; -+ case WM_DESTROY: -+ PostQuitMessage(0); -+ break; -+ default: -+ break; -+ } -+ -+ return 1; -+} -+ -+static pj_status_t create_os_messages_window(struct wmme_factory *wf) -+{ -+ pj_status_t status = PJ_EBUG; -+ WNDCLASSEX wndClass; -+ HWND hWnd; -+ -+ /* Set up and register window class */ -+ ZeroMemory(&wndClass, sizeof(WNDCLASSEX)); -+ wndClass.cbSize = sizeof(WNDCLASSEX); -+ wndClass.style = CS_OWNDC; -+ wndClass.lpfnWndProc = (WNDPROC)(ProcessOSMessage); -+ wndClass.hInstance = (HINSTANCE)(GetModuleHandle(0)); -+ wndClass.lpszClassName = "DeviceChangeMessageWindow"; -+ -+ if (RegisterClassEx(&wndClass)) { -+ /* Create the window that will receive OS messages */ -+ hWnd = CreateWindowEx( 0, "DeviceChangeMessageWindow", NULL, 0, 0, 0, 0, 0, NULL, NULL, NULL, (LPVOID)(wf)); -+ if (hWnd != NULL) { -+ wf->dev_observer.hWnd = hWnd; -+ if (UpdateWindow(hWnd) != 0) { -+ status = PJ_SUCCESS; -+ } -+ } else { -+ PJ_LOG(4,(THIS_FILE, "Error creating window to receive device change events")); -+ } -+ } -+ -+ return status; -+ -+} -+ -+static pj_status_t dispatch_os_messages(void) -+{ -+ pj_status_t status = PJ_SUCCESS; -+ MSG msg; -+ int ret; -+ -+ /* Process OS messages with low cpu-usage wait loop */ -+ while((ret = GetMessage(&msg, NULL, 0, 0)) != 0) { -+ if (ret == -1) { -+ PJ_LOG(4,(THIS_FILE, "Couldn't process OS message")); -+ status = PJ_EBUG; -+ break; -+ } else { -+ TranslateMessage(&msg); -+ DispatchMessage(&msg); -+ } -+ } -+ -+ return status; -+ -+} -+ -+/* WMME device observer thread thread. */ -+static int PJ_THREAD_FUNC wmme_dev_observer_thread(void *arg) -+{ -+ struct wmme_factory *wf = (struct wmme_factory*)arg; -+ pj_status_t status; -+ -+ status = create_os_messages_window(wf); -+ if (status == PJ_SUCCESS) { -+ status = dispatch_os_messages(); -+ if (status != PJ_SUCCESS) { -+ PJ_LOG(4,(THIS_FILE, "Error dispatching device detection window events")); -+ } -+ } else { -+ PJ_LOG(4,(THIS_FILE, "Failed to create window for receiving device detection events")); -+ } -+ -+ return status; -+} -+ -+/* API: set audio device change observer */ -+static void factory_set_observer(pjmedia_aud_dev_factory *f, -+ pjmedia_aud_dev_change_callback cb) -+{ -+ struct wmme_factory *wf = (struct wmme_factory*)f; -+ pj_pool_t *pool; -+ pj_status_t status; -+ -+ if (cb) { -+ pool = pj_pool_create(wf->pf, "wmme-dev-observer", 1000, 1000, NULL); -+ PJ_ASSERT_ON_FAIL(pool != NULL, {return;}); -+ status = pj_thread_create(pool, "wmme_observer", &wmme_dev_observer_thread, wf, 0, 0, &wf->dev_observer.thread); -+ if (status != PJ_SUCCESS) { -+ PJ_LOG(4,(THIS_FILE, "Failed to create WMME device detection thread")); -+ wf->dev_observer.thread = NULL; -+ return; -+ } -+ wf->dev_observer.cb = cb; -+ } else { -+ wf->dev_observer.cb = NULL; -+ if (wf->dev_observer.hWnd) { -+ CloseWindow(wf->dev_observer.hWnd); -+ wf->dev_observer.hWnd = NULL; -+ } -+ pj_thread_join(wf->dev_observer.thread); -+ pj_thread_destroy(wf->dev_observer.thread); -+ wf->dev_observer.thread = NULL; -+ } -+} -+ -+/* API: get default recording device */ -+static int factory_get_default_rec_dev(pjmedia_aud_dev_factory *f) -+{ -+ PJ_UNUSED_ARG(f); -+ /* Let PJMEDIA pick the first one available */ -+ return -1; -+} -+ -+/* API: get default playback device */ -+static int factory_get_default_play_dev(pjmedia_aud_dev_factory *f) -+{ -+ PJ_UNUSED_ARG(f); -+ /* Let PJMEDIA pick the first one available */ -+ return -1; -+} -+ - /* API: Get stream info. */ - static pj_status_t stream_get_param(pjmedia_aud_stream *s, - pjmedia_aud_param *pi) -Index: pjmedia/src/pjmedia-codec/audio_codecs.c -=================================================================== ---- pjmedia/src/pjmedia-codec/audio_codecs.c (revision 5249) -+++ pjmedia/src/pjmedia-codec/audio_codecs.c (working copy) -@@ -86,13 +86,6 @@ - return status; - #endif /* PJMEDIA_HAS_G722_CODEC */ - --#if PJMEDIA_HAS_INTEL_IPP -- /* Register IPP codecs */ -- status = pjmedia_codec_ipp_init(endpt); -- if (status != PJ_SUCCESS) -- return status; --#endif /* PJMEDIA_HAS_INTEL_IPP */ -- - #if PJMEDIA_HAS_G7221_CODEC - /* Register G722.1 codecs */ - status = pjmedia_codec_g7221_init(endpt); -@@ -100,33 +93,12 @@ - return status; - #endif /* PJMEDIA_HAS_G7221_CODEC */ - --#if PJMEDIA_HAS_L16_CODEC -- /* Register L16 family codecs */ -- status = pjmedia_codec_l16_init(endpt, 0); -- if (status != PJ_SUCCESS) -- return status; --#endif /* PJMEDIA_HAS_L16_CODEC */ -- --#if PJMEDIA_HAS_OPENCORE_AMRNB_CODEC || PJMEDIA_HAS_OPENCORE_AMRWB_CODEC -- /* Register OpenCORE AMR */ -- status = pjmedia_codec_opencore_amr_init(endpt, 0); -- if (status != PJ_SUCCESS) -- return status; --#endif -- --#if PJMEDIA_HAS_SILK_CODEC -- /* Register SILK */ -- status = pjmedia_codec_silk_init(endpt); -- if (status != PJ_SUCCESS) -- return status; --#endif -- - #if PJMEDIA_HAS_OPUS_CODEC -- /* Register OPUS */ -+ /* Register opus codecs */ - status = pjmedia_codec_opus_init(endpt); - if (status != PJ_SUCCESS) - return status; --#endif -+#endif /* PJMEDIA_HAS_OPUS_CODEC */ - - return PJ_SUCCESS; - } -Index: pjmedia/src/pjmedia-codec/ffmpeg_vid_codecs.c -=================================================================== ---- pjmedia/src/pjmedia-codec/ffmpeg_vid_codecs.c (revision 5249) -+++ pjmedia/src/pjmedia-codec/ffmpeg_vid_codecs.c (working copy) -@@ -62,16 +62,7 @@ - #endif - - #if LIBAVCODEC_VER_AT_LEAST(53,61) --# if LIBAVCODEC_VER_AT_LEAST(54,59) -- /* Not sure when AVCodec::encode is obsoleted/removed. */ --# define AVCODEC_HAS_ENCODE(c) (c->encode2) --# else -- /* Not sure when AVCodec::encode2 is introduced. It appears in -- * libavcodec 53.61 where some codecs actually still use AVCodec::encode -- * (e.g: H263, H264). -- */ --# define AVCODEC_HAS_ENCODE(c) (c->encode || c->encode2) --# endif -+# define AVCODEC_HAS_ENCODE(c) (c->encode2) - # define AV_OPT_SET(obj,name,val,opt) (av_opt_set(obj,name,val,opt)==0) - # define AV_OPT_SET_INT(obj,name,val) (av_opt_set_int(obj,name,val,0)==0) - #else -@@ -266,6 +257,7 @@ - /* H264 constants */ - #define PROFILE_H264_BASELINE 66 - #define PROFILE_H264_MAIN 77 -+#define PROFILE_H264_HIGH 100 - - /* Codec specific functions */ - #if PJMEDIA_HAS_FFMPEG_CODEC_H264 -@@ -403,6 +395,9 @@ - case PROFILE_H264_MAIN: - profile = "main"; - break; -+ case PROFILE_H264_HIGH: -+ profile = "high"; -+ break; - default: - break; - } -@@ -439,11 +434,11 @@ - /* Misc x264 settings (performance, quality, latency, etc). - * Let's just use the x264 predefined preset & tune. - */ -- if (!AV_OPT_SET(ctx->priv_data, "preset", "veryfast", 0)) { -+ if (!AV_OPT_SET(ctx->priv_data, "preset", "ultrafast", 0)) { - PJ_LOG(3, (THIS_FILE, "Failed to set x264 preset 'veryfast'")); - } -- if (!AV_OPT_SET(ctx->priv_data, "tune", "animation+zerolatency", 0)) { -- PJ_LOG(3, (THIS_FILE, "Failed to set x264 tune 'zerolatency'")); -+ if (!AV_OPT_SET(ctx->priv_data, "tune", "fastdecode+zerolatency", 0)) { -+ PJ_LOG(3, (THIS_FILE, "Failed to set x264 tune 'fastdecode+zerolatency'")); - } - } - -@@ -1474,7 +1469,7 @@ - } else { - output->size = err; - output->bit_info = 0; -- if (ff->enc_ctx->coded_frame->key_frame) -+ if (avpacket.flags & AV_PKT_FLAG_KEY) - output->bit_info |= PJMEDIA_VID_FRM_KEYFRAME; - } - -@@ -1689,12 +1684,12 @@ - avpacket.size = (int)input->size; - - /* ffmpeg warns: -- * - input buffer padding, at least FF_INPUT_BUFFER_PADDING_SIZE -+ * - input buffer padding, at least AV_INPUT_BUFFER_PADDING_SIZE - * - null terminated - * Normally, encoded buffer is allocated more than needed, so lets just - * bzero the input buffer end/pad, hope it will be just fine. - */ -- pj_bzero(avpacket.data+avpacket.size, FF_INPUT_BUFFER_PADDING_SIZE); -+ pj_bzero(avpacket.data+avpacket.size, AV_INPUT_BUFFER_PADDING_SIZE); - - output->bit_info = 0; - output->timestamp = input->timestamp; -Index: pjmedia/src/pjmedia-codec/openh264.cpp -=================================================================== ---- pjmedia/src/pjmedia-codec/openh264.cpp (revision 5249) -+++ pjmedia/src/pjmedia-codec/openh264.cpp (working copy) -@@ -163,8 +163,8 @@ - - struct SLayerPEncCtx - { -- pj_int32_t iDLayerQp; -- SSliceConfig sSliceCfg; -+ pj_int32_t iDLayerQp; -+ SSliceArgument sSliceArgument; - }; - - PJ_DEF(pj_status_t) pjmedia_codec_openh264_vid_init(pjmedia_vid_codec_mgr *mgr, -@@ -470,18 +470,19 @@ - */ - - /* Init encoder parameters */ -- pj_bzero(&eprm, sizeof(eprm)); -- eprm.iInputCsp = videoFormatI420; -+ oh264_data->enc->GetDefaultParams (&eprm); -+ eprm.iComplexityMode = MEDIUM_COMPLEXITY; - eprm.sSpatialLayers[0].uiProfileIdc = PRO_BASELINE; - eprm.iPicWidth = param->enc_fmt.det.vid.size.w; -+ eprm.iUsageType = CAMERA_VIDEO_REAL_TIME; - eprm.iPicHeight = param->enc_fmt.det.vid.size.h; - eprm.fMaxFrameRate = (param->enc_fmt.det.vid.fps.num * - 1.0f / - param->enc_fmt.det.vid.fps.denum); -- eprm.uiFrameToBeCoded = (unsigned int) -1; - eprm.iTemporalLayerNum = 1; - eprm.uiIntraPeriod = 0; /* I-Frame interval in frames */ -- eprm.bEnableSpsPpsIdAddition = (oh264_data->whole? false : true); -+ eprm.eSpsPpsIdStrategy = (oh264_data->whole ? CONSTANT_ID : -+ INCREASING_ID); - eprm.bEnableFrameCroppingFlag = true; - eprm.iLoopFilterDisableIdc = 0; - eprm.iLoopFilterAlphaC0Offset = 0; -@@ -504,18 +505,19 @@ - - pj_bzero(&elayer_ctx, sizeof (SLayerPEncCtx)); - elayer_ctx.iDLayerQp = 24; -- elayer_ctx.sSliceCfg.uiSliceMode = (oh264_data->whole ? -- SM_SINGLE_SLICE : SM_DYN_SLICE); -- elayer_ctx.sSliceCfg.sSliceArgument.uiSliceSizeConstraint = param->enc_mtu; -- elayer_ctx.sSliceCfg.sSliceArgument.uiSliceNum = 1; -- elayer_ctx.sSliceCfg.sSliceArgument.uiSliceMbNum[0] = 960; -- elayer_ctx.sSliceCfg.sSliceArgument.uiSliceMbNum[1] = 0; -- elayer_ctx.sSliceCfg.sSliceArgument.uiSliceMbNum[2] = 0; -- elayer_ctx.sSliceCfg.sSliceArgument.uiSliceMbNum[3] = 0; -- elayer_ctx.sSliceCfg.sSliceArgument.uiSliceMbNum[4] = 0; -- elayer_ctx.sSliceCfg.sSliceArgument.uiSliceMbNum[5] = 0; -- elayer_ctx.sSliceCfg.sSliceArgument.uiSliceMbNum[6] = 0; -- elayer_ctx.sSliceCfg.sSliceArgument.uiSliceMbNum[7] = 0; -+ elayer_ctx.sSliceArgument.uiSliceMode = (oh264_data->whole ? -+ SM_SINGLE_SLICE : -+ SM_SIZELIMITED_SLICE); -+ elayer_ctx.sSliceArgument.uiSliceSizeConstraint = param->enc_mtu; -+ elayer_ctx.sSliceArgument.uiSliceNum = 1; -+ elayer_ctx.sSliceArgument.uiSliceMbNum[0] = 960; -+ elayer_ctx.sSliceArgument.uiSliceMbNum[1] = 0; -+ elayer_ctx.sSliceArgument.uiSliceMbNum[2] = 0; -+ elayer_ctx.sSliceArgument.uiSliceMbNum[3] = 0; -+ elayer_ctx.sSliceArgument.uiSliceMbNum[4] = 0; -+ elayer_ctx.sSliceArgument.uiSliceMbNum[5] = 0; -+ elayer_ctx.sSliceArgument.uiSliceMbNum[6] = 0; -+ elayer_ctx.sSliceArgument.uiSliceMbNum[7] = 0; - - elayer->iVideoWidth = eprm.iPicWidth; - elayer->iVideoHeight = eprm.iPicHeight; -@@ -523,14 +525,14 @@ - elayer->uiProfileIdc = eprm.sSpatialLayers[0].uiProfileIdc; - elayer->iSpatialBitrate = eprm.iTargetBitrate; - elayer->iDLayerQp = elayer_ctx.iDLayerQp; -- elayer->sSliceCfg.uiSliceMode = elayer_ctx.sSliceCfg.uiSliceMode; -+ elayer->sSliceArgument.uiSliceMode = elayer_ctx.sSliceArgument.uiSliceMode; - -- memcpy( &elayer->sSliceCfg, -- &elayer_ctx.sSliceCfg, -- sizeof (SSliceConfig)); -- memcpy( &elayer->sSliceCfg.sSliceArgument.uiSliceMbNum[0], -- &elayer_ctx.sSliceCfg.sSliceArgument.uiSliceMbNum[0], -- sizeof (elayer_ctx.sSliceCfg.sSliceArgument.uiSliceMbNum)); -+ memcpy ( &elayer->sSliceArgument, -+ &elayer_ctx.sSliceArgument, -+ sizeof (SSliceArgument)); -+ memcpy ( &elayer->sSliceArgument.uiSliceMbNum[0], -+ &elayer_ctx.sSliceArgument.uiSliceMbNum[0], -+ sizeof (elayer_ctx.sSliceArgument.uiSliceMbNum)); - - /* Init input picture */ - oh264_data->esrc_pic->iColorFormat = videoFormatI420; -@@ -552,13 +554,20 @@ - return PJMEDIA_CODEC_EFAILED; - } - -+ int videoFormat = videoFormatI420; -+ rc = oh264_data->enc->SetOption (ENCODER_OPTION_DATAFORMAT, &videoFormat); -+ if (rc != cmResultSuccess) { -+ PJ_LOG(4,(THIS_FILE, "SVC encoder SetOption videoFormatI420 failed, " -+ "rc=%d", rc)); -+ return PJMEDIA_CODEC_EFAILED; -+ } -+ - /* - * Decoder - */ - sDecParam.sVideoProperty.size = sizeof (sDecParam.sVideoProperty); -- sDecParam.iOutputColorFormat = videoFormatI420; - sDecParam.uiTargetDqLayer = (pj_uint8_t) - 1; -- sDecParam.uiEcActiveFlag = 1; -+ sDecParam.eEcActiveIdc = ERROR_CON_SLICE_COPY; - sDecParam.sVideoProperty.eVideoBsType = VIDEO_BITSTREAM_DEFAULT; - - //TODO: -@@ -576,14 +585,6 @@ - return PJMEDIA_CODEC_EFAILED; - } - -- pj_int32_t color_fmt = videoFormatI420; -- rc = oh264_data->dec->SetOption (DECODER_OPTION_DATAFORMAT, &color_fmt); -- if (rc) { -- PJ_LOG(4,(THIS_FILE, -- "Warning: SetOption(DECODER_OPTION_DATAFORMAT) failed, rc=%d", -- rc)); -- } -- - oh264_data->dec_buf_size = (MAX_RX_WIDTH * MAX_RX_HEIGHT * 3 >> 1) + - (MAX_RX_WIDTH); - oh264_data->dec_buf = (pj_uint8_t*)pj_pool_alloc(oh264_data->pool, -@@ -661,7 +662,7 @@ - return PJMEDIA_CODEC_EFAILED; - } - -- if (oh264_data->bsi.eOutputFrameType == videoFrameTypeSkip) { -+ if (oh264_data->bsi.eFrameType == videoFrameTypeSkip) { - output->size = 0; - output->type = PJMEDIA_FRAME_TYPE_NONE; - output->timestamp = input->timestamp; -@@ -681,13 +682,13 @@ - - /* Find which layer with biggest payload */ - oh264_data->ilayer = 0; -- payload_size = oh264_data->bsi.sLayerInfo[0].iNalLengthInByte[0]; -+ payload_size = oh264_data->bsi.sLayerInfo[0].pNalLengthInByte[0]; - for (i=0; i < (unsigned)oh264_data->bsi.iLayerNum; ++i) { - unsigned j; - pLayerBsInfo = &oh264_data->bsi.sLayerInfo[i]; - for (j=0; j < (unsigned)pLayerBsInfo->iNalCount; ++j) { -- if (pLayerBsInfo->iNalLengthInByte[j] > (int)payload_size) { -- payload_size = pLayerBsInfo->iNalLengthInByte[j]; -+ if (pLayerBsInfo->pNalLengthInByte[j] > (int)payload_size) { -+ payload_size = pLayerBsInfo->pNalLengthInByte[j]; - oh264_data->ilayer = i; - } - } -@@ -703,7 +704,7 @@ - payload = pLayerBsInfo->pBsBuf; - payload_size = 0; - for (int inal = pLayerBsInfo->iNalCount - 1; inal >= 0; --inal) { -- payload_size += pLayerBsInfo->iNalLengthInByte[inal]; -+ payload_size += pLayerBsInfo->pNalLengthInByte[inal]; - } - - if (payload_size > out_size) -@@ -760,7 +761,7 @@ - pj_memcpy(output->buf, payload, payload_len); - output->size = payload_len; - -- if (oh264_data->bsi.eOutputFrameType == videoFrameTypeIDR) { -+ if (oh264_data->bsi.eFrameType == videoFrameTypeIDR) { - output->bit_info |= PJMEDIA_VID_FRM_KEYFRAME; - } - -@@ -782,7 +783,7 @@ - - oh264_data->enc_frame_size = 0; - for (int inal = pLayerBsInfo->iNalCount - 1; inal >= 0; --inal) { -- oh264_data->enc_frame_size += pLayerBsInfo->iNalLengthInByte[inal]; -+ oh264_data->enc_frame_size += pLayerBsInfo->pNalLengthInByte[inal]; - } - - oh264_data->enc_frame_whole = pLayerBsInfo->pBsBuf; -@@ -809,7 +810,7 @@ - pj_memcpy(output->buf, payload, payload_len); - output->size = payload_len; - -- if (oh264_data->bsi.eOutputFrameType == videoFrameTypeIDR) { -+ if (oh264_data->bsi.eFrameType == videoFrameTypeIDR) { - output->bit_info |= PJMEDIA_VID_FRM_KEYFRAME; - } - -Index: pjmedia/src/pjmedia-codec/opus.c -=================================================================== ---- pjmedia/src/pjmedia-codec/opus.c (revision 5249) -+++ pjmedia/src/pjmedia-codec/opus.c (working copy) -@@ -1,1070 +1,762 @@ --/* $Id$ */ --/* -- * Copyright (C) 2015-2016 Teluu Inc. (http://www.teluu.com) -- * Copyright (C) 2012-2015 Zaark Technology AB -+/** -+ * Copyright (C) 2010 Regis Montoya (aka r3gis - www.r3gis.fr) -+ * This file is part of pjsip_android. - * -- * 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. -+ * Licensed under the Apache License, Version 2.0 (the "License"); -+ * you may not use this file except in compliance with the License. -+ * You may obtain a copy of the License at - * -- * 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. -+ * http://www.apache.org/licenses/LICENSE-2.0 - * -- * 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 -+ * Unless required by applicable law or agreed to in writing, software -+ * distributed under the License is distributed on an "AS IS" BASIS, -+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -+ * See the License for the specific language governing permissions and -+ * limitations under the License. - */ --/* This file is the implementation of Opus codec wrapper and was contributed by -- * Zaark Technology AB -- */ - - #include -+#include -+#include -+#include - #include --#include -+#include -+#include -+#include -+#include - #include - - #if defined(PJMEDIA_HAS_OPUS_CODEC) && (PJMEDIA_HAS_OPUS_CODEC!=0) - --#include -+#include "../../third_party/opus/include/opus.h" - --#define THIS_FILE "opus.c" -+/* Opus can encode frames of 2.5, 5, 10, 20, 40, or 60 ms. */ -+#define FRAME_LENGTH_MS 20 - --/* Default packet loss concealment setting. */ --#define OPUS_DEFAULT_PLC 1 --/* Default Voice Activity Detector setting. */ --#define OPUS_DEFAULT_VAD 0 -+/* It can also combine multiple frames into packets of up to 120 ms */ -+/* So at maximum 2.5ms * 48frames = 120ms*/ -+#define OPUS_MAX_FRAMES_PER_PACKET 48 - --/* Maximum size of an encoded packet. -- * If the the actual size is bigger, the encode/parse will fail. -- */ --#define MAX_ENCODED_PACKET_SIZE 1280 -+#define OPUS_CLOCK_RATE 48000 - --/* Tracing */ --#if 0 --# define TRACE_(expr) PJ_LOG(4,expr) --#else --# define TRACE_(expr) --#endif -+#define _TRACE_OPUS 0 - -+#define THIS_FILE "opus.c" - --/* Prototypes for Opus factory */ --static pj_status_t factory_test_alloc( pjmedia_codec_factory *factory, -- const pjmedia_codec_info *ci ); --static pj_status_t factory_default_attr( pjmedia_codec_factory *factory, -- const pjmedia_codec_info *ci, -- pjmedia_codec_param *attr ); --static pj_status_t factory_enum_codecs( pjmedia_codec_factory *factory, -- unsigned *count, -- pjmedia_codec_info codecs[]); --static pj_status_t factory_alloc_codec( pjmedia_codec_factory *factory, -- const pjmedia_codec_info *ci, -- pjmedia_codec **p_codec); --static pj_status_t factory_dealloc_codec( pjmedia_codec_factory *factory, -- pjmedia_codec *codec ); -+/* Prototypes for OPUS factory */ -+static pj_status_t opus_test_alloc(pjmedia_codec_factory *factory, -+ const pjmedia_codec_info *id); -+static pj_status_t opus_default_attr(pjmedia_codec_factory *factory, -+ const pjmedia_codec_info *id, -+ pjmedia_codec_param *attr); -+static pj_status_t opus_enum_codecs(pjmedia_codec_factory *factory, -+ unsigned *count, -+ pjmedia_codec_info codecs[]); -+static pj_status_t opus_alloc_codec(pjmedia_codec_factory *factory, -+ const pjmedia_codec_info *id, -+ pjmedia_codec **p_codec); -+static pj_status_t opus_dealloc_codec(pjmedia_codec_factory *factory, -+ pjmedia_codec *codec); - -+/* Prototypes for OPUS implementation. */ -+static pj_status_t opus_codec_init(pjmedia_codec *codec, -+ pj_pool_t *pool); -+static pj_status_t opus_codec_open(pjmedia_codec *codec, -+ pjmedia_codec_param *attr); -+static pj_status_t opus_codec_close(pjmedia_codec *codec); -+static pj_status_t opus_codec_modify(pjmedia_codec *codec, -+ const pjmedia_codec_param *attr); -+static pj_status_t opus_codec_parse(pjmedia_codec *codec, -+ void *pkt, -+ pj_size_t pkt_size, -+ const pj_timestamp *timestamp, -+ unsigned *frame_cnt, -+ pjmedia_frame frames[]); -+static pj_status_t opus_codec_encode(pjmedia_codec *codec, -+ const struct pjmedia_frame *input, -+ unsigned output_buf_len, -+ struct pjmedia_frame *output); -+static pj_status_t opus_codec_decode(pjmedia_codec *codec, -+ const struct pjmedia_frame *input, -+ unsigned output_buf_len, -+ struct pjmedia_frame *output); -+static pj_status_t opus_codec_recover(pjmedia_codec *codec, -+ unsigned output_buf_len, -+ struct pjmedia_frame *output); - --/* Prototypes for Opus implementation. */ --static pj_status_t codec_init( pjmedia_codec *codec, -- pj_pool_t *pool ); --static pj_status_t codec_open( pjmedia_codec *codec, -- pjmedia_codec_param *attr ); --static pj_status_t codec_close( pjmedia_codec *codec ); --static pj_status_t codec_modify( pjmedia_codec *codec, -- const pjmedia_codec_param *attr ); --static pj_status_t 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 codec_encode( pjmedia_codec *codec, -- const struct pjmedia_frame *input, -- unsigned output_buf_len, -- struct pjmedia_frame *output); --static pj_status_t codec_decode( pjmedia_codec *codec, -- const struct pjmedia_frame *input, -- unsigned output_buf_len, -- struct pjmedia_frame *output); --static pj_status_t codec_recover( pjmedia_codec *codec, -- unsigned output_buf_len, -- struct pjmedia_frame *output); -- --/* Definition for Opus operations. */ --static pjmedia_codec_op opus_op = --{ -- &codec_init, -- &codec_open, -- &codec_close, -- &codec_modify, -- &codec_parse, -- &codec_encode, -- &codec_decode, -- &codec_recover -+/* Definition for OPUS codec operations. */ -+static pjmedia_codec_op opus_op = { -+ &opus_codec_init, -+ &opus_codec_open, -+ &opus_codec_close, -+ &opus_codec_modify, -+ &opus_codec_parse, -+ &opus_codec_encode, -+ &opus_codec_decode, -+ &opus_codec_recover - }; - --/* Definition for Opus factory operations. */ --static pjmedia_codec_factory_op opus_factory_op = --{ -- &factory_test_alloc, -- &factory_default_attr, -- &factory_enum_codecs, -- &factory_alloc_codec, -- &factory_dealloc_codec, -+/* Definition for OPUS codec factory operations. */ -+static pjmedia_codec_factory_op opus_factory_op = { -+ &opus_test_alloc, -+ &opus_default_attr, -+ &opus_enum_codecs, -+ &opus_alloc_codec, -+ &opus_dealloc_codec, - &pjmedia_codec_opus_deinit - }; - -+/* OPUS factory private data */ -+static struct opus_factory { -+ pjmedia_codec_factory base; -+ pjmedia_endpt *endpt; -+ pj_pool_t *pool; -+ pj_mutex_t *mutex; -+ pjmedia_codec codec_list; -+} opus_factory; - --/* Opus factory */ --struct opus_codec_factory --{ -- pjmedia_codec_factory base; -- pjmedia_endpt *endpt; -- pj_pool_t *pool; --}; -+/* OPUS codec private data. */ -+struct opus_private { -+ pj_pool_t *pool; /* Pool for each instance. */ -+ pj_uint8_t pcm_bytes_per_sample; - --/* Opus codec private data. */ --struct opus_data --{ -- pj_pool_t *pool; -- pj_mutex_t *mutex; -- OpusEncoder *enc; -- OpusDecoder *dec; -- OpusRepacketizer *enc_packer; -- OpusRepacketizer *dec_packer; -- pjmedia_codec_opus_config cfg; -- unsigned ptime; -- pjmedia_frame dec_frame[2]; -- int dec_frame_index; --}; -+ int externalFs; /* Clock rate we would like to limit from outside */ - --/* Codec factory instance */ --static struct opus_codec_factory opus_codec_factory; -+ pj_bool_t enc_ready; -+ OpusEncoder* psEnc; - --/* Opus default configuration */ --static pjmedia_codec_opus_config opus_cfg = --{ -- PJMEDIA_CODEC_OPUS_DEFAULT_SAMPLE_RATE, /* Sample rate */ -- 1, /* Channel count */ -- PJMEDIA_CODEC_OPUS_DEFAULT_BIT_RATE, /* Bit rate */ -- 5, /* Expected packet loss */ -- PJMEDIA_CODEC_OPUS_DEFAULT_COMPLEXITY, /* Complexity */ -- PJMEDIA_CODEC_OPUS_DEFAULT_CBR, /* Constant bit rate */ -+ pj_bool_t dec_ready; -+ OpusDecoder* psDec; -+ -+ /* Buffer of 120ms to hold decoded frames. */ -+ void *dec_buf; -+ pj_size_t dec_buf_size; -+ pj_size_t dec_buf_max_size; -+ int dec_buf_sample_per_frame; -+ pj_uint32_t pkt_info; /* Packet info for buffered frames. */ - }; - -- --static int get_opus_bw_constant (unsigned sample_rate) --{ -- if (sample_rate <= 8000) -- return OPUS_BANDWIDTH_NARROWBAND; -- else if (sample_rate <= 12000) -- return OPUS_BANDWIDTH_MEDIUMBAND; -- else if (sample_rate <= 16000) -- return OPUS_BANDWIDTH_WIDEBAND; -- else if (sample_rate <= 24000) -- return OPUS_BANDWIDTH_SUPERWIDEBAND; -- else -- return OPUS_BANDWIDTH_FULLBAND; -+int opus_to_pjsip_error_code(int opus_error) { -+ switch (opus_error) { -+ case OPUS_BAD_ARG: -+ /* One or more invalid/out of range arguments */ -+ return PJ_EINVAL; -+ case OPUS_BUFFER_TOO_SMALL: -+ /* The mode struct passed is invalid */ -+ return PJMEDIA_CODEC_EPCMTOOSHORT; -+ case OPUS_INTERNAL_ERROR: -+ /* An internal error was detected */ -+ return PJMEDIA_CODEC_EFAILED; -+ case OPUS_INVALID_PACKET: -+ /* The compressed data passed is corrupted */ -+ return PJMEDIA_CODEC_EBADBITSTREAM; -+ case OPUS_UNIMPLEMENTED: -+ /* Invalid/unsupported request number */ -+ return PJ_ENOTSUP; -+ case OPUS_INVALID_STATE: -+ /* An encoder or decoder structure is invalid or already freed */ -+ return PJ_EINVALIDOP; -+ case OPUS_ALLOC_FAIL: -+ /* Memory allocation has failed */ -+ return PJMEDIA_CODEC_EFAILED; -+ } -+ return PJMEDIA_ERROR; - } - -- - /* -- * Initialize and register Opus codec factory to pjmedia endpoint. -+ * Apply opus settings to dec_fmtp parameters - */ --PJ_DEF(pj_status_t) pjmedia_codec_opus_init( pjmedia_endpt *endpt ) --{ -- pj_status_t status; -- pjmedia_codec_mgr *codec_mgr; -+void apply_opus_codec_params(pj_pool_t* pool, pjmedia_codec_param *attr) { -+ attr->setting.dec_fmtp.cnt = 0; -+ attr->setting.dec_fmtp.param[attr->setting.dec_fmtp.cnt].name = pj_str("useinbandfec"); -+ if (attr->setting.plc == 0) { -+ attr->setting.dec_fmtp.param[attr->setting.dec_fmtp.cnt].val = pj_str("0"); -+ } else { -+ attr->setting.dec_fmtp.param[attr->setting.dec_fmtp.cnt].val = pj_str("1"); -+ } -+ attr->setting.dec_fmtp.cnt++; -+ if (attr->setting.vad == 1) { -+ attr->setting.dec_fmtp.param[attr->setting.dec_fmtp.cnt].name = pj_str("usedtx"); -+ attr->setting.dec_fmtp.param[attr->setting.dec_fmtp.cnt].val = pj_str("1"); -+ attr->setting.dec_fmtp.cnt++; -+ } -+ if (attr->info.channel_cnt == 2) { -+ attr->setting.dec_fmtp.param[attr->setting.dec_fmtp.cnt].name = pj_str("stereo"); -+ attr->setting.dec_fmtp.param[attr->setting.dec_fmtp.cnt].val = pj_str("1"); -+ attr->setting.dec_fmtp.cnt++; -+ } -+ if (attr->info.clock_rate < 48000) { -+ attr->setting.dec_fmtp.param[attr->setting.dec_fmtp.cnt].name = pj_str("maxcodedaudiobandwidth"); -+ char clock_rate_char[8]; -+ pj_utoa(attr->info.clock_rate, clock_rate_char); -+ pj_strdup2(pool, &attr->setting.dec_fmtp.param[attr->setting.dec_fmtp.cnt].val, clock_rate_char); -+ attr->setting.dec_fmtp.cnt++; -+ } -+} - -- PJ_ASSERT_RETURN(endpt, PJ_EINVAL); -+PJ_DEF(pj_status_t) pjmedia_codec_opus_init(pjmedia_endpt *endpt) { -+ pjmedia_codec_mgr *codec_mgr; -+ pj_status_t status; - -- if (opus_codec_factory.pool != NULL) -- return PJ_SUCCESS; -+ if (opus_factory.endpt != NULL) { -+ /* Already initialized. */ -+ return PJ_SUCCESS; -+ } - -- /* Create the Opus codec factory */ -- opus_codec_factory.base.op = &opus_factory_op; -- opus_codec_factory.base.factory_data = &opus_codec_factory; -- opus_codec_factory.endpt = endpt; -+ /* Init factory */ -+ opus_factory.base.op = &opus_factory_op; -+ opus_factory.base.factory_data = NULL; -+ opus_factory.endpt = endpt; - -- opus_codec_factory.pool = pjmedia_endpt_create_pool(endpt, "opus-factory", -- 1024, 1024); -- if (!opus_codec_factory.pool) { -- PJ_LOG(2, (THIS_FILE, "Unable to create memory pool for Opus codec")); -- return PJ_ENOMEM; -- } -+ /* Create pool */ -+ opus_factory.pool = pjmedia_endpt_create_pool(endpt, "opus codecs", 4000, 4000); -+ if (!opus_factory.pool) -+ return PJ_ENOMEM; - -- /* Get the codec manager */ -- codec_mgr = pjmedia_endpt_get_codec_mgr(endpt); -- if (!codec_mgr) { -- PJ_LOG(2, (THIS_FILE, "Unable to get the codec manager")); -- status = PJ_EINVALIDOP; -- goto on_codec_factory_error; -- } -+ /* Init list */ -+ pj_list_init(&opus_factory.codec_list); - -- /* Register the codec factory */ -- status = pjmedia_codec_mgr_register_factory (codec_mgr, -- &opus_codec_factory.base); -- if (status != PJ_SUCCESS) { -- PJ_LOG(2, (THIS_FILE, "Unable to register the codec factory")); -- goto on_codec_factory_error; -- } -+ /* Create mutex. */ -+ status = pj_mutex_create_simple(opus_factory.pool, "opus codecs", &opus_factory.mutex); -+ if (status != PJ_SUCCESS) -+ goto on_error; - -- return PJ_SUCCESS; -+ PJ_LOG(5, (THIS_FILE, "Init opus")); - --on_codec_factory_error: -- pj_pool_release(opus_codec_factory.pool); -- opus_codec_factory.pool = NULL; -- return status; --} -+ /* Get the codec manager. */ -+ codec_mgr = pjmedia_endpt_get_codec_mgr(endpt); -+ if (!codec_mgr) -+ return PJ_EINVALIDOP; - - --/* -- * Unregister Opus codec factory from pjmedia endpoint and -- * deinitialize the codec. -- */ --PJ_DEF(pj_status_t) pjmedia_codec_opus_deinit( void ) --{ -- pj_status_t status; -- pjmedia_codec_mgr *codec_mgr; -+ PJ_LOG(5, (THIS_FILE, "Init opus > DONE")); - -- if (opus_codec_factory.pool == NULL) -+ /* Register codec factory to endpoint. */ -+ status = pjmedia_codec_mgr_register_factory(codec_mgr, &opus_factory.base); -+ if (status != PJ_SUCCESS) -+ return status; -+ - return PJ_SUCCESS; - -- /* Get the codec manager */ -- codec_mgr = pjmedia_endpt_get_codec_mgr(opus_codec_factory.endpt); -- if (!codec_mgr) { -- PJ_LOG(2, (THIS_FILE, "Unable to get the codec manager")); -- pj_pool_release(opus_codec_factory.pool); -- opus_codec_factory.pool = NULL; -- return PJ_EINVALIDOP; -- } -+on_error: -+ if (opus_factory.mutex) { -+ pj_mutex_destroy(opus_factory.mutex); -+ opus_factory.mutex = NULL; -+ } -+ if (opus_factory.pool) { -+ pj_pool_release(opus_factory.pool); -+ opus_factory.pool = NULL; -+ } - -- /* Unregister the codec factory */ -- status = pjmedia_codec_mgr_unregister_factory(codec_mgr, -- &opus_codec_factory.base); -- if (status != PJ_SUCCESS) -- PJ_LOG(2, (THIS_FILE, "Unable to unregister the codec factory")); -- -- /* Release the memory pool */ -- pj_pool_release(opus_codec_factory.pool); -- opus_codec_factory.pool = NULL; -- -- return status; -+ return status; - } - -- --/** -- * Get the opus configuration for a specific sample rate. -+/* -+ * Unregister OPUS codec factory from pjmedia endpoint and deinitialize -+ * the OPUS codec library. - */ --PJ_DEF(pj_status_t) --pjmedia_codec_opus_get_config( pjmedia_codec_opus_config *cfg ) --{ -- PJ_ASSERT_RETURN(cfg, PJ_EINVAL); -+PJ_DEF(pj_status_t) pjmedia_codec_opus_deinit(void) { -+ pjmedia_codec_mgr *codec_mgr; -+ pj_status_t status; - -- pj_memcpy(cfg, &opus_cfg, sizeof(pjmedia_codec_opus_config)); -- return PJ_SUCCESS; --} -+ if (opus_factory.endpt == NULL) { -+ /* Not registered. */ -+ return PJ_SUCCESS; -+ } - -+ /* Lock mutex. */ -+ pj_mutex_lock(opus_factory.mutex); - --static pj_str_t STR_MAX_PLAYBACK = {"maxplaybackrate", 15}; --static pj_str_t STR_MAX_CAPTURE = {"sprop-maxcapturerate", 20}; --static pj_str_t STR_STEREO = {"stereo", 6}; --static pj_str_t STR_SPROP_STEREO = {"sprop-stereo", 12}; --static pj_str_t STR_MAX_BIT_RATE = {"maxaveragebitrate", 17}; --static pj_str_t STR_INBAND_FEC = {"useinbandfec", 12}; --static pj_str_t STR_DTX = {"usedtx", 6}; --static pj_str_t STR_CBR = {"cbr", 3}; -- --static int find_fmtp(pjmedia_codec_fmtp *fmtp, pj_str_t *name, pj_bool_t add) --{ -- int i; -- for (i = 0; i < fmtp->cnt; i++) { -- if (pj_stricmp(&fmtp->param[i].name, name) == 0) -- return i; -- } -- -- if (add && (i < PJMEDIA_CODEC_MAX_FMTP_CNT)) { -- fmtp->param[i].name = *name; -- fmtp->cnt++; -- return i; -- } else -- return -1; --} -- --static pj_status_t generate_fmtp(pjmedia_codec_param *attr) --{ -- int idx; -- static char bitrate_str[12]; -- static char clockrate_str[12]; -- -- if (attr->info.clock_rate != 48000) { -- pj_ansi_snprintf(clockrate_str, sizeof(clockrate_str), "%u", -- attr->info.clock_rate); -- -- idx = find_fmtp(&attr->setting.dec_fmtp, &STR_MAX_PLAYBACK, PJ_TRUE); -- if (idx >= 0) -- attr->setting.dec_fmtp.param[idx].val = pj_str(clockrate_str); -- -- idx = find_fmtp(&attr->setting.dec_fmtp, &STR_MAX_CAPTURE, PJ_TRUE); -- if (idx >= 0) -- attr->setting.dec_fmtp.param[idx].val = pj_str(clockrate_str); -- } -- -- /* Check if we need to set parameter 'maxaveragebitrate' */ -- if (opus_cfg.bit_rate > 0) { -- idx = find_fmtp(&attr->setting.dec_fmtp, &STR_MAX_BIT_RATE, PJ_TRUE); -- if (idx >= 0) { -- pj_ansi_snprintf(bitrate_str, sizeof(bitrate_str), "%u", -- attr->info.avg_bps); -- attr->setting.dec_fmtp.param[idx].val = pj_str(bitrate_str); -+ /* Get the codec manager. */ -+ codec_mgr = pjmedia_endpt_get_codec_mgr(opus_factory.endpt); -+ if (!codec_mgr) { -+ opus_factory.endpt = NULL; -+ pj_mutex_unlock(opus_factory.mutex); -+ return PJ_EINVALIDOP; - } -- } - -- if (attr->info.channel_cnt > 1) { -- idx = find_fmtp(&attr->setting.dec_fmtp, &STR_STEREO, PJ_TRUE); -- if (idx >= 0) -- attr->setting.dec_fmtp.param[idx].val = pj_str("1"); -+ /* Unregister opus codec factory. */ -+ status = pjmedia_codec_mgr_unregister_factory(codec_mgr, &opus_factory.base); -+ opus_factory.endpt = NULL; - -- idx = find_fmtp(&attr->setting.dec_fmtp, &STR_SPROP_STEREO, PJ_TRUE); -- if (idx >= 0) -- attr->setting.dec_fmtp.param[idx].val = pj_str("1"); -- } -+ /* Destroy mutex. */ -+ pj_mutex_unlock(opus_factory.mutex); -+ pj_mutex_destroy(opus_factory.mutex); -+ opus_factory.mutex = NULL; - -- if (opus_cfg.cbr) { -- idx = find_fmtp(&attr->setting.dec_fmtp, &STR_CBR, PJ_TRUE); -- if (idx >= 0) -- attr->setting.dec_fmtp.param[idx].val = pj_str("1"); -- } -+ /* Release pool. */ -+ pj_pool_release(opus_factory.pool); -+ opus_factory.pool = NULL; - -- if (attr->setting.plc) { -- idx = find_fmtp(&attr->setting.dec_fmtp, &STR_INBAND_FEC, PJ_TRUE); -- if (idx >= 0) -- attr->setting.dec_fmtp.param[idx].val = pj_str("1"); -- } -- -- if (attr->setting.vad) { -- idx = find_fmtp(&attr->setting.dec_fmtp, &STR_DTX, PJ_TRUE); -- if (idx >= 0) -- attr->setting.dec_fmtp.param[idx].val = pj_str("1"); -- } -- -- return PJ_SUCCESS; -+ return status; - } - --/** -- * Set the opus configuration and default param. -+/* -+ * Check if factory can allocate the specified codec. - */ --PJ_DEF(pj_status_t) --pjmedia_codec_opus_set_default_param(const pjmedia_codec_opus_config *cfg, -- pjmedia_codec_param *param ) --{ -- const pj_str_t opus_str = {"opus", 4}; -- const pjmedia_codec_info *info[1]; -- pjmedia_codec_mgr *codec_mgr; -- unsigned count = 1; -- pj_status_t status; -+static pj_status_t opus_test_alloc(pjmedia_codec_factory *factory, const pjmedia_codec_info *info) { -+ const pj_str_t opus_tag = { "opus", 4 }; - -- TRACE_((THIS_FILE, "%s:%d: - TRACE", __FUNCTION__, __LINE__)); -- PJ_ASSERT_RETURN(cfg, PJ_EINVAL); -+ PJ_UNUSED_ARG(factory); -+ PJ_ASSERT_RETURN(factory==&opus_factory.base, PJ_EINVAL); - -- codec_mgr = pjmedia_endpt_get_codec_mgr(opus_codec_factory.endpt); -+ /* Type MUST be audio. */ -+ if (info->type != PJMEDIA_TYPE_AUDIO) -+ return PJMEDIA_CODEC_EUNSUP; - -- status = pjmedia_codec_mgr_find_codecs_by_id(codec_mgr, &opus_str, -- &count, info, NULL); -- if (status != PJ_SUCCESS) -- return status; -+ /* Check encoding name. */ -+ if (pj_stricmp(&info->encoding_name, &opus_tag) != 0) -+ return PJMEDIA_CODEC_EUNSUP; - -- /* Set sample rate */ -- if (cfg->sample_rate != 8000 && cfg->sample_rate != 12000 && -- cfg->sample_rate != 16000 && cfg->sample_rate != 24000 && -- cfg->sample_rate != 48000) -- { -- return PJ_EINVAL; -- } -- param->info.clock_rate = opus_cfg.sample_rate = cfg->sample_rate; -+ /* Check clock-rate */ -+ if (info->clock_rate == 8000 || info->clock_rate == 12000 || -+ info->clock_rate == 16000 || info->clock_rate == 24000 || info->clock_rate == 48000) { -+ return PJ_SUCCESS; -+ } - -- /* Set channel count */ -- if (cfg->channel_cnt != 1 && cfg->channel_cnt != 2) -- return PJ_EINVAL; -- param->info.channel_cnt = opus_cfg.channel_cnt = cfg->channel_cnt; -- -- /* Set bit_rate */ -- if (cfg->bit_rate < 6000 || cfg->bit_rate > 510000) { -- return PJ_EINVAL; -- } -- opus_cfg.bit_rate = cfg->bit_rate; -- -- /* Set expected packet loss */ -- if (cfg->packet_loss >= 100) -- return PJ_EINVAL; -- opus_cfg.packet_loss = cfg->packet_loss; -- -- /* Set complexity */ -- if (cfg->complexity > 10) -- return PJ_EINVAL; -- opus_cfg.complexity = cfg->complexity; -- -- opus_cfg.cbr = cfg->cbr; -- -- generate_fmtp(param); -- -- status = pjmedia_codec_mgr_set_default_param(codec_mgr, info[0], param); -- return status; -+ /* Clock rate not supported */ -+ return PJMEDIA_CODEC_EUNSUP; - } - -- - /* -- * Check if factory can allocate the specified codec. -+ * Generate default attribute. - */ --static pj_status_t factory_test_alloc( pjmedia_codec_factory *factory, -- const pjmedia_codec_info *ci ) --{ -- const pj_str_t opus_tag = {"OPUS", 4}; -+static pj_status_t opus_default_attr(pjmedia_codec_factory *factory, const pjmedia_codec_info *id, pjmedia_codec_param *attr) { -+ PJ_ASSERT_RETURN(factory == &opus_factory.base, PJ_EINVAL); -+ pj_bzero(attr, sizeof(pjmedia_codec_param)); - -- PJ_UNUSED_ARG(factory); -- PJ_ASSERT_RETURN(factory==&opus_codec_factory.base, PJ_EINVAL); -+ /* Table from opus rfc -+ +-------+---------+-----------+ -+ | Mode | fs (Hz) | BR (kbps) | -+ +-------+---------+-----------+ -+ | voice | 8000 | 6 - 20 | -+ | voice | 12000 | 7 - 25 | -+ | voice | 16000 | 8 - 30 | -+ | voice | 24000 | 18 - 28 | -+ | voice | 48000 | 24 - 32 | -+ +-------+---------+-----------+ -+ */ - -- /* Type MUST be audio. */ -- if (ci->type != PJMEDIA_TYPE_AUDIO) -- return PJMEDIA_CODEC_EUNSUP; -+ attr->info.channel_cnt = 1; -+ /* SAGH: set to 2? */ -+ /* -+ * TODO : would like to use 16kHz as internal clock rate in our case -+ * pjmedia seems to have no support of different clock rate for RTP -+ * and for associated port. Keeping 48kHz for RTP is needed (we just have -+ * to transform timestamps) but to feed codec with 16kHz frames seems requires -+ * some extra work in pjmedia. -+ * For now we are obliged to use pjmedia resampler while would be -+ * more efficient to use the Opus feature instead. -+ * Using g722 hack was tried but seems useless. -+ */ -+ attr->info.clock_rate = 48000; -+ attr->info.avg_bps = 20000; -+ attr->info.max_bps = 32000; -+ attr->info.frm_ptime = FRAME_LENGTH_MS; -+ attr->info.pcm_bits_per_sample = 16; -+ attr->info.pt = (pj_uint8_t) id->pt; - -- /* Check encoding name. */ -- if (pj_stricmp(&ci->encoding_name, &opus_tag) != 0) -- return PJMEDIA_CODEC_EUNSUP; -+ /* Inform the stream to prepare a larger buffer since we cannot parse -+ * OPUS packets and split it into individual frames. -+ * Max packet size of opus is 120ms audio -+ */ -+ attr->info.max_rx_frame_size = attr->info.max_bps * 120 / 8 / 1000; -+ if ((attr->info.max_bps * attr->info.frm_ptime) % 8000 != 0) -+ ++attr->info.max_rx_frame_size; - -- /* Check clock rate */ -- if (ci->clock_rate != 8000 && ci->clock_rate != 12000 && -- ci->clock_rate != 16000 && ci->clock_rate != 24000 && -- ci->clock_rate != 48000) -- { -- return PJMEDIA_CODEC_EUNSUP; -- } - -- return PJ_SUCCESS; --} -+ attr->setting.frm_per_pkt = 1; -+ /* Default usedtx is 0 in opus */ -+ attr->setting.vad = 0; -+ /* Default useinbandfec is 1 in opus */ -+ attr->setting.plc = 1; - -+ /* Apply these settings to relevant fmtp parameters */ -+ apply_opus_codec_params(opus_factory.pool, attr); - --/* -- * Generate default attribute. -- */ --static pj_status_t factory_default_attr( pjmedia_codec_factory *factory, -- const pjmedia_codec_info *ci, -- pjmedia_codec_param *attr ) --{ -- TRACE_((THIS_FILE, "%s:%d: - TRACE", __FUNCTION__, __LINE__)); -- -- pj_bzero(attr, sizeof(pjmedia_codec_param)); -- attr->info.pt = (pj_uint8_t)ci->pt; -- attr->info.clock_rate = opus_cfg.sample_rate; -- attr->info.channel_cnt = opus_cfg.channel_cnt; -- attr->info.avg_bps = (opus_cfg.bit_rate > 0) ? -- opus_cfg.bit_rate : -- opus_cfg.sample_rate; // Estimate -- attr->info.max_bps = opus_cfg.bit_rate * 2; -- attr->info.frm_ptime = 20; -- attr->setting.frm_per_pkt = 1; -- attr->info.pcm_bits_per_sample = 16; -- attr->setting.vad = OPUS_DEFAULT_VAD; -- attr->setting.plc = OPUS_DEFAULT_PLC; -- -- generate_fmtp(attr); -- -- return PJ_SUCCESS; -+ return PJ_SUCCESS; - } - -- - /* - * Enum codecs supported by this factory. - */ --static pj_status_t factory_enum_codecs( pjmedia_codec_factory *factory, -- unsigned *count, -- pjmedia_codec_info codecs[] ) --{ -- PJ_UNUSED_ARG(factory); -- PJ_ASSERT_RETURN(codecs, PJ_EINVAL); -+static pj_status_t opus_enum_codecs(pjmedia_codec_factory *factory, unsigned *count, pjmedia_codec_info codecs[]) { -+ PJ_UNUSED_ARG(factory); -+ PJ_ASSERT_RETURN(codecs && *count > 0, PJ_EINVAL); - -- if (*count > 0) { - pj_bzero(&codecs[0], sizeof(pjmedia_codec_info)); -- codecs[0].type = PJMEDIA_TYPE_AUDIO; -- codecs[0].pt = PJMEDIA_RTP_PT_OPUS; -- /* -- * RFC 7587, Section 7: -- * The media subtype ("opus") goes in SDP "a=rtpmap" as the encoding -- * name. The RTP clock rate in "a=rtpmap" MUST be 48000 and the -- * number of channels MUST be 2. -- */ - codecs[0].encoding_name = pj_str("opus"); -- codecs[0].clock_rate = 48000; -- codecs[0].channel_cnt = 2; -+ codecs[0].pt = PJMEDIA_RTP_PT_OPUS; -+ codecs[0].type = PJMEDIA_TYPE_AUDIO; -+ codecs[0].clock_rate = 48000; -+ codecs[0].channel_cnt = 1; /* SAGHUL: set to 2? */ -+ - *count = 1; -- } - -- return PJ_SUCCESS; -+ return PJ_SUCCESS; -+ - } - -- - /* -- * Allocate a new Opus codec instance. -+ * Allocate a new OPUS codec instance. - */ --static pj_status_t factory_alloc_codec( pjmedia_codec_factory *factory, -- const pjmedia_codec_info *ci, -- pjmedia_codec **p_codec ) --{ -- pjmedia_codec *codec; -- pj_pool_t *pool; -- pj_status_t status; -- struct opus_data *opus_data; -- struct opus_codec_factory *f = (struct opus_codec_factory*) factory; -+static pj_status_t opus_alloc_codec(pjmedia_codec_factory *factory, const pjmedia_codec_info *id, pjmedia_codec **p_codec) { -+ pjmedia_codec *codec; -+ struct opus_private *opus; - -- TRACE_((THIS_FILE, "%s:%d: - TRACE", __FUNCTION__, __LINE__)); -+ PJ_ASSERT_RETURN(factory && id && p_codec, PJ_EINVAL); -+ PJ_ASSERT_RETURN(factory == &opus_factory.base, PJ_EINVAL); - -- pool = pjmedia_endpt_create_pool(f->endpt, "opus", 512, 512); -- if (!pool) return PJ_ENOMEM; -- -- opus_data = PJ_POOL_ZALLOC_T(pool, struct opus_data); -- codec = PJ_POOL_ZALLOC_T(pool, pjmedia_codec); -+ pj_mutex_lock(opus_factory.mutex); - -- status = pj_mutex_create_simple (pool, "opus_mutex", &opus_data->mutex); -- if (status != PJ_SUCCESS) { -- pj_pool_release(pool); -- return status; -- } -+ /* Get free nodes, if any. */ -+ if (!pj_list_empty(&opus_factory.codec_list)) { -+ codec = opus_factory.codec_list.next; -+ pj_list_erase(codec); -+ } else { -+ codec = PJ_POOL_ZALLOC_T(opus_factory.pool, pjmedia_codec); -+ PJ_ASSERT_RETURN(codec != NULL, PJ_ENOMEM); -+ codec->op = &opus_op; -+ codec->factory = factory; -+ codec->codec_data = pj_pool_alloc(opus_factory.pool, sizeof(struct opus_private)); -+ } - -- pj_memcpy(&opus_data->cfg, &opus_cfg, sizeof(pjmedia_codec_opus_config)); -- opus_data->pool = pool; -- codec->op = &opus_op; -- codec->factory = factory; -- codec->codec_data = opus_data; -+ pj_mutex_unlock(opus_factory.mutex); - -- *p_codec = codec; -- return PJ_SUCCESS; -+ opus = (struct opus_private*) codec->codec_data; -+ opus->enc_ready = PJ_FALSE; -+ opus->dec_ready = PJ_FALSE; -+ -+ /* Create pool for codec instance */ -+ opus->pool = pjmedia_endpt_create_pool(opus_factory.endpt, "opuscodec", 512, 512); -+ -+ *p_codec = codec; -+ return PJ_SUCCESS; - } - -- - /* - * Free codec. - */ --static pj_status_t factory_dealloc_codec( pjmedia_codec_factory *factory, -- pjmedia_codec *codec ) --{ -- struct opus_data *opus_data; -+static pj_status_t opus_dealloc_codec(pjmedia_codec_factory *factory, pjmedia_codec *codec) { -+ struct opus_private *opus; - -- PJ_ASSERT_RETURN(factory && codec, PJ_EINVAL); -- PJ_ASSERT_RETURN(factory == &opus_codec_factory.base, PJ_EINVAL); -+ PJ_ASSERT_RETURN(factory && codec, PJ_EINVAL); -+ PJ_UNUSED_ARG(factory); -+ PJ_ASSERT_RETURN(factory == &opus_factory.base, PJ_EINVAL); - -- opus_data = (struct opus_data *)codec->codec_data; -- if (opus_data) { -- pj_mutex_destroy(opus_data->mutex); -- opus_data->mutex = NULL; -- pj_pool_release(opus_data->pool); -- } -+ opus = (struct opus_private*) codec->codec_data; - -- return PJ_SUCCESS; -+ /* Close codec, if it's not closed. */ -+ if (opus->enc_ready || opus->dec_ready) -+ opus_codec_close(codec); -+ -+ /* Put in the free list. */ -+ pj_mutex_lock(opus_factory.mutex); -+ pj_list_push_front(&opus_factory.codec_list, codec); -+ pj_mutex_unlock(opus_factory.mutex); -+ -+ pj_pool_release(opus->pool); -+ opus->pool = NULL; -+ -+ return PJ_SUCCESS; - } - -- - /* - * Init codec. - */ --static pj_status_t codec_init( pjmedia_codec *codec, -- pj_pool_t *pool ) --{ -- PJ_UNUSED_ARG(codec); -- PJ_UNUSED_ARG(pool); -- return PJ_SUCCESS; -+static pj_status_t opus_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 codec_open( pjmedia_codec *codec, -- pjmedia_codec_param *attr ) --{ -- struct opus_data *opus_data = (struct opus_data *)codec->codec_data; -- int idx, err; -+static pj_status_t opus_codec_open(pjmedia_codec *codec, pjmedia_codec_param *attr) { -+ const pj_str_t STR_FMTP_USE_INBAND_FEC = { "useinbandfec", 12 }; -+ const pj_str_t STR_FMTP_MAX_AVERAGE_BITRATE = { "maxaveragebitrate", 17 }; -+ const pj_str_t STR_FMTP_MAX_CODED_AUDIO_BANDWIDTH = { "maxcodedaudiobandwidth", 22 }; -+ const pj_str_t STR_FMTP_USE_DTX = { "usedtx", 6 }; - -- PJ_ASSERT_RETURN(codec && attr && opus_data, PJ_EINVAL); -+ struct opus_private *opus; -+ int ret, tmpFmtpVal; -+ unsigned i, structSizeBytes, max_nsamples; - -- pj_mutex_lock (opus_data->mutex); -+ opus = (struct opus_private*) codec->codec_data; - -- TRACE_((THIS_FILE, "%s:%d: - TRACE", __FUNCTION__, __LINE__)); -+ PJ_ASSERT_RETURN(opus && !opus->enc_ready && !opus->dec_ready, PJ_EINVAL); - -- opus_data->cfg.sample_rate = attr->info.clock_rate; -- opus_data->cfg.channel_cnt = attr->info.channel_cnt; -- opus_data->ptime = attr->info.frm_ptime; -+ PJ_LOG(4, (THIS_FILE, "Clock rate is %d ", attr->info.clock_rate)); -+ opus->externalFs = attr->info.clock_rate; - -- /* Allocate memory used by the codec */ -- if (!opus_data->enc) { -- /* Allocate memory for max 2 channels */ -- opus_data->enc = pj_pool_zalloc(opus_data->pool, -- opus_encoder_get_size(2)); -- } -- if (!opus_data->dec) { -- /* Allocate memory for max 2 channels */ -- opus_data->dec = pj_pool_zalloc(opus_data->pool, -- opus_decoder_get_size(2)); -- } -- if (!opus_data->enc_packer) { -- opus_data->enc_packer = pj_pool_zalloc(opus_data->pool, -- opus_repacketizer_get_size()); -- } -- if (!opus_data->dec_packer) { -- opus_data->dec_packer = pj_pool_zalloc(opus_data->pool, -- opus_repacketizer_get_size()); -- } -- if (!opus_data->enc || !opus_data->dec || -- !opus_data->enc_packer || !opus_data->dec_packer) -- { -- PJ_LOG(2, (THIS_FILE, "Unable to allocate memory for the codec")); -- pj_mutex_unlock (opus_data->mutex); -- return PJ_ENOMEM; -- } -+ /* Create Encoder */ -+ structSizeBytes = opus_encoder_get_size(attr->info.channel_cnt); -+ opus->psEnc = pj_pool_zalloc(opus->pool, structSizeBytes); -+ ret = opus_encoder_init(opus->psEnc, opus->externalFs, attr->info.channel_cnt, OPUS_APPLICATION_AUDIO); -+ if (ret) { -+ PJ_LOG(1, (THIS_FILE, "Unable to init encoder : %d", ret)); -+ return PJ_EINVAL; -+ } - -- /* Check max average bit rate */ -- idx = find_fmtp(&attr->setting.enc_fmtp, &STR_MAX_BIT_RATE, PJ_FALSE); -- if (idx >= 0) { -- unsigned rate; -- rate = (unsigned)pj_strtoul(&attr->setting.enc_fmtp.param[idx].val); -- if (rate < attr->info.avg_bps) -- attr->info.avg_bps = rate; -- } -+ /* -+ * Set Encoder parameters -+ * TODO : have it configurable -+ */ -+ opus_encoder_ctl(opus->psEnc, OPUS_SET_COMPLEXITY(10)); -+ opus_encoder_ctl(opus->psEnc, OPUS_SET_INBAND_FEC(1)); /* on by default */ -+ opus_encoder_ctl(opus->psEnc, OPUS_SET_PACKET_LOSS_PERC(5)); -+ opus_encoder_ctl(opus->psEnc, OPUS_SET_SIGNAL(OPUS_AUTO)); - -- /* Check plc */ -- idx = find_fmtp(&attr->setting.enc_fmtp, &STR_INBAND_FEC, PJ_FALSE); -- if (idx >= 0) { -- unsigned plc; -- plc = (unsigned) pj_strtoul(&attr->setting.enc_fmtp.param[idx].val); -- attr->setting.plc = plc > 0? PJ_TRUE: PJ_FALSE; -- } -+ /* Apply fmtp params to Encoder */ -+ for (i = 0; i < attr->setting.enc_fmtp.cnt; ++i) { -+ if (pj_stricmp(&attr->setting.enc_fmtp.param[i].name, &STR_FMTP_USE_INBAND_FEC) == 0) { -+ tmpFmtpVal = (int)(pj_strtoul(&attr->setting.enc_fmtp.param[i].val)); -+ opus_encoder_ctl(opus->psEnc, OPUS_SET_INBAND_FEC(tmpFmtpVal)); -+ break; -+ } else if (pj_stricmp(&attr->setting.enc_fmtp.param[i].name, &STR_FMTP_MAX_AVERAGE_BITRATE) == 0) { -+ tmpFmtpVal = (int)(pj_strtoul(&attr->setting.enc_fmtp.param[i].val)); -+ if (tmpFmtpVal >= 6000 && tmpFmtpVal <= 510000) { -+ opus_encoder_ctl(opus->psEnc, OPUS_SET_BITRATE(tmpFmtpVal)); -+ } -+ } else if (pj_stricmp(&attr->setting.enc_fmtp.param[i].name, &STR_FMTP_MAX_CODED_AUDIO_BANDWIDTH) == 0) { -+ tmpFmtpVal = (int)(pj_strtoul(&attr->setting.enc_fmtp.param[i].val)); -+ if (tmpFmtpVal <= 8000) { -+ opus_encoder_ctl(opus->psEnc, OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_NARROWBAND)); -+ } else if (tmpFmtpVal <= 12000) { -+ opus_encoder_ctl(opus->psEnc, OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_MEDIUMBAND)); -+ } else if (tmpFmtpVal <= 16000) { -+ opus_encoder_ctl(opus->psEnc, OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_WIDEBAND)); -+ } else if (tmpFmtpVal <= 24000) { -+ opus_encoder_ctl(opus->psEnc, OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_SUPERWIDEBAND)); -+ } else if (tmpFmtpVal <= 48000) { -+ opus_encoder_ctl(opus->psEnc, OPUS_SET_MAX_BANDWIDTH(OPUS_BANDWIDTH_FULLBAND)); -+ } -+ } else if (pj_stricmp(&attr->setting.enc_fmtp.param[i].name, &STR_FMTP_USE_DTX) == 0) { -+ tmpFmtpVal = (int)(pj_strtoul(&attr->setting.enc_fmtp.param[i].val)); -+ opus_encoder_ctl(opus->psEnc, OPUS_SET_DTX(tmpFmtpVal)); -+ } -+ } - -- /* Check vad */ -- idx = find_fmtp(&attr->setting.enc_fmtp, &STR_DTX, PJ_FALSE); -- if (idx >= 0) { -- unsigned vad; -- vad = (unsigned) pj_strtoul(&attr->setting.enc_fmtp.param[idx].val); -- attr->setting.vad = vad > 0? PJ_TRUE: PJ_FALSE; -- } -+ opus->enc_ready = PJ_TRUE; - -- /* Check cbr */ -- idx = find_fmtp(&attr->setting.enc_fmtp, &STR_CBR, PJ_FALSE); -- if (idx >= 0) { -- unsigned cbr; -- cbr = (unsigned) pj_strtoul(&attr->setting.enc_fmtp.param[idx].val); -- opus_data->cfg.cbr = cbr > 0? PJ_TRUE: PJ_FALSE; -- } -- -- /* Check max average bit rate */ -- idx = find_fmtp(&attr->setting.dec_fmtp, &STR_MAX_BIT_RATE, PJ_FALSE); -- if (idx >= 0) { -- unsigned rate; -- rate = (unsigned) pj_strtoul(&attr->setting.dec_fmtp.param[idx].val); -- if (rate < attr->info.avg_bps) -- attr->info.avg_bps = rate; -- } -+ /* Decoder buffer */ -+ opus->pcm_bytes_per_sample = attr->info.pcm_bits_per_sample / 8; -+ max_nsamples = 120 * OPUS_CLOCK_RATE / 1000; /* 120ms is max frame time */ -+ opus->dec_buf_max_size = max_nsamples * opus->pcm_bytes_per_sample; -+ opus->dec_buf = pj_pool_alloc(opus->pool, opus->dec_buf_max_size); - -- TRACE_((THIS_FILE, "%s:%d: sample_rate: %u", -- __FUNCTION__, __LINE__, opus_data->cfg.sample_rate)); -+ /* Create decoder */ -+ structSizeBytes = opus_decoder_get_size(attr->info.channel_cnt); -+ opus->psDec = pj_pool_zalloc(opus->pool, structSizeBytes); -+ ret = opus_decoder_init(opus->psDec, opus->externalFs, attr->info.channel_cnt); -+ if (ret) { -+ PJ_LOG(1, (THIS_FILE, "Unable to init decoder : %d", ret)); -+ return PJ_EINVAL; -+ } - -- /* Initialize encoder */ -- err = opus_encoder_init(opus_data->enc, -- opus_data->cfg.sample_rate, -- attr->info.channel_cnt, -- OPUS_APPLICATION_VOIP); -- if (err != OPUS_OK) { -- PJ_LOG(2, (THIS_FILE, "Unable to create encoder")); -- return PJMEDIA_CODEC_EFAILED; -- } -- -- /* Set signal type */ -- opus_encoder_ctl(opus_data->enc, OPUS_SET_SIGNAL(OPUS_SIGNAL_VOICE)); -- /* Set bitrate */ -- opus_encoder_ctl(opus_data->enc, OPUS_SET_BITRATE(attr->info.avg_bps)); -- /* Set VAD */ -- opus_encoder_ctl(opus_data->enc, OPUS_SET_DTX(attr->setting.vad ? 1 : 0)); -- /* Set PLC */ -- opus_encoder_ctl(opus_data->enc, -- OPUS_SET_INBAND_FEC(attr->setting.plc ? 1 : 0)); -- /* Set bandwidth */ -- opus_encoder_ctl(opus_data->enc, -- OPUS_SET_MAX_BANDWIDTH(get_opus_bw_constant( -- opus_data->cfg.sample_rate))); -- /* Set expected packet loss */ -- opus_encoder_ctl(opus_data->enc, -- OPUS_SET_PACKET_LOSS_PERC(opus_data->cfg.packet_loss)); -- /* Set complexity */ -- opus_encoder_ctl(opus_data->enc, -- OPUS_SET_COMPLEXITY(opus_data->cfg.complexity)); -- /* Set constant bit rate */ -- opus_encoder_ctl(opus_data->enc, -- OPUS_SET_VBR(opus_data->cfg.cbr ? 0 : 1)); -+ opus->dec_ready = PJ_TRUE; - -- PJ_LOG(5, (THIS_FILE, "Initialize Opus encoder, sample rate: %d, " -- "avg bitrate: %d, vad: %d, plc: %d, pkt loss: %d, " -- "complexity: %d, constant bit rate: %d", -- opus_data->cfg.sample_rate, -- attr->info.avg_bps, attr->setting.vad?1:0, -- attr->setting.plc?1:0, -- opus_data->cfg.packet_loss, -- opus_data->cfg.complexity, -- opus_data->cfg.cbr?1:0)); -- -- /* Initialize decoder */ -- err = opus_decoder_init (opus_data->dec, -- opus_data->cfg.sample_rate, -- attr->info.channel_cnt); -- if (err != OPUS_OK) { -- PJ_LOG(2, (THIS_FILE, "Unable to initialize decoder")); -- return PJMEDIA_CODEC_EFAILED; -- } -- -- /* Initialize temporary decode frames used for FEC */ -- opus_data->dec_frame[0].type = PJMEDIA_FRAME_TYPE_NONE; -- opus_data->dec_frame[0].buf = pj_pool_zalloc(opus_data->pool, -- (opus_data->cfg.sample_rate / 1000) -- * 60 * attr->info.channel_cnt * 2 /* bytes per sample */); -- opus_data->dec_frame[1].type = PJMEDIA_FRAME_TYPE_NONE; -- opus_data->dec_frame[1].buf = pj_pool_zalloc(opus_data->pool, -- (opus_data->cfg.sample_rate / 1000) -- * 60 * attr->info.channel_cnt * 2 /* bytes per sample */); -- opus_data->dec_frame_index = -1; -- -- /* Initialize the repacketizers */ -- opus_repacketizer_init(opus_data->enc_packer); -- opus_repacketizer_init(opus_data->dec_packer); -- -- pj_mutex_unlock (opus_data->mutex); -- return PJ_SUCCESS; -+ return PJ_SUCCESS; - } - -- - /* - * Close codec. - */ --static pj_status_t codec_close( pjmedia_codec *codec ) --{ -- PJ_UNUSED_ARG(codec); -- return PJ_SUCCESS; -+static pj_status_t opus_codec_close(pjmedia_codec *codec) { -+ struct opus_private *opus; -+ opus = (struct opus_private*) codec->codec_data; -+ -+ opus->enc_ready = PJ_FALSE; -+ opus->dec_ready = PJ_FALSE; -+ -+ PJ_LOG(5, (THIS_FILE, "OPUS codec closed")); -+ return PJ_SUCCESS; - } - -- - /* - * Modify codec settings. - */ --static pj_status_t codec_modify( pjmedia_codec *codec, -- const pjmedia_codec_param *attr ) --{ -- struct opus_data *opus_data = (struct opus_data *)codec->codec_data; -+static pj_status_t opus_codec_modify(pjmedia_codec *codec, const pjmedia_codec_param *attr) { -+ PJ_TODO(implement_opus_codec_modify); - -- pj_mutex_lock (opus_data->mutex); -+ PJ_UNUSED_ARG(codec); -+ PJ_UNUSED_ARG(attr); - -- TRACE_((THIS_FILE, "%s:%d: - TRACE", __FUNCTION__, __LINE__)); -- -- /* Set bitrate */ -- opus_data->cfg.bit_rate = attr->info.avg_bps; -- opus_encoder_ctl(opus_data->enc, OPUS_SET_BITRATE(attr->info.avg_bps)); -- /* Set VAD */ -- opus_encoder_ctl(opus_data->enc, OPUS_SET_DTX(attr->setting.vad ? 1 : 0)); -- /* Set PLC */ -- opus_encoder_ctl(opus_data->enc, -- OPUS_SET_INBAND_FEC(attr->setting.plc ? 1 : 0)); -- -- pj_mutex_unlock (opus_data->mutex); - return PJ_SUCCESS; - } - -- - /* -- * Get frames in the packet. -+ * Encode frame. - */ --static pj_status_t codec_parse( pjmedia_codec *codec, -- void *pkt, -- pj_size_t pkt_size, -- const pj_timestamp *ts, -- unsigned *frame_cnt, -- pjmedia_frame frames[] ) --{ -- struct opus_data *opus_data = (struct opus_data *)codec->codec_data; -- unsigned char tmp_buf[MAX_ENCODED_PACKET_SIZE]; -- int i, num_frames; -- int size, out_pos; -- unsigned samples_per_frame; --#if (USE_INCOMING_WORSE_SETTINGS) -- int bw; --#endif -+static pj_status_t opus_codec_encode(pjmedia_codec *codec, const struct pjmedia_frame *input, unsigned output_buf_len, struct pjmedia_frame *output) { -+ struct opus_private *opus; -+ opus_int32 ret; -+ unsigned nsamples; - -- pj_mutex_lock (opus_data->mutex); -+ PJ_ASSERT_RETURN(codec && input && output, PJ_EINVAL); - -- if (pkt_size > sizeof(tmp_buf)) { -- PJ_LOG(5, (THIS_FILE, "Encoded size bigger than buffer")); -- pj_mutex_unlock (opus_data->mutex); -- return PJMEDIA_CODEC_EFRMTOOSHORT; -- } -+ opus = (struct opus_private*) codec->codec_data; - -- samples_per_frame = (opus_data->cfg.sample_rate * -- opus_data->ptime) / 1000; -+ /* Check frame in size */ -+ nsamples = input->size / opus->pcm_bytes_per_sample; -+ /* TODO: validate? */ - -- pj_memcpy(tmp_buf, pkt, pkt_size); -+ /* Encode */ -+ output->size = 0; - -- opus_repacketizer_init(opus_data->dec_packer); -- opus_repacketizer_cat(opus_data->dec_packer, tmp_buf, pkt_size); -- -- num_frames = opus_repacketizer_get_nb_frames(opus_data->dec_packer); -- out_pos = 0; -- for (i = 0; i < num_frames; ++i) { -- size = opus_repacketizer_out_range(opus_data->dec_packer, i, i+1, -- ((unsigned char*)pkt) + out_pos, -- sizeof(tmp_buf)); -- if (size < 0) { -- PJ_LOG(5, (THIS_FILE, "Parse failed! (%d)", pkt_size)); -- pj_mutex_unlock (opus_data->mutex); -- return PJMEDIA_CODEC_EFAILED; -+ ret = opus_encode(opus->psEnc, (opus_int16*) input->buf, nsamples, (unsigned char *) output->buf, output_buf_len); -+ if (ret < 0) { -+ PJ_LOG(1, (THIS_FILE, "Impossible to encode packet %d", ret)); -+ return opus_to_pjsip_error_code(ret); -+ } else { -+ output->size = (pj_size_t) ret; - } -- frames[i].type = PJMEDIA_FRAME_TYPE_AUDIO; -- frames[i].buf = ((char*)pkt) + out_pos; -- frames[i].size = size; -- frames[i].timestamp.u64 = ts->u64 + i * samples_per_frame; -- out_pos += size; -- } -- *frame_cnt = num_frames; -- -- pj_mutex_unlock (opus_data->mutex); -- return PJ_SUCCESS; -+ output->type = PJMEDIA_FRAME_TYPE_AUDIO; -+ output->timestamp = input->timestamp; -+#if _TRACE_OPUS -+ PJ_LOG(4, (THIS_FILE, "Encoder packet size %d for input %d ouput max len %d @ %d", output->size, input->size, output_buf_len, (unsigned) output->timestamp.u64)); -+#endif -+ return PJ_SUCCESS; - } - -- - /* -- * Encode frame. -+ * Get frames in the packet. - */ --static pj_status_t codec_encode( pjmedia_codec *codec, -- const struct pjmedia_frame *input, -- unsigned output_buf_len, -- struct pjmedia_frame *output ) --{ -- struct opus_data *opus_data = (struct opus_data *)codec->codec_data; -- opus_int32 size = 0; -- unsigned in_pos = 0; -- unsigned out_pos = 0; -- unsigned frame_size; -- unsigned samples_per_frame; -- unsigned char tmp_buf[MAX_ENCODED_PACKET_SIZE]; -- unsigned tmp_bytes_left = sizeof(tmp_buf); - -- pj_mutex_lock (opus_data->mutex); -+static pj_status_t opus_codec_parse(pjmedia_codec *codec, void *pkt, pj_size_t pkt_size, const pj_timestamp *ts, unsigned *frame_cnt, pjmedia_frame frames[]) { -+ struct opus_private *opus; -+ unsigned char toc; -+ const unsigned char *raw_frames[48]; -+ short size[48]; -+ int err, payload_offset, samples_per_frame; -+ unsigned i; - -- samples_per_frame = (opus_data->cfg.sample_rate * -- opus_data->ptime) / 1000; -- frame_size = samples_per_frame * opus_data->cfg.channel_cnt * -- sizeof(opus_int16); -+ PJ_ASSERT_RETURN(frame_cnt, PJ_EINVAL); - -- opus_repacketizer_init(opus_data->enc_packer); -- while (input->size - in_pos >= frame_size) { -- size = opus_encode(opus_data->enc, -- (const opus_int16*)(((char*)input->buf) + in_pos), -- samples_per_frame, -- tmp_buf + out_pos, -- (tmp_bytes_left < frame_size ? -- tmp_bytes_left : frame_size)); -- if (size < 0) { -- PJ_LOG(4, (THIS_FILE, "Encode failed! (%d)", size)); -- pj_mutex_unlock (opus_data->mutex); -- return PJMEDIA_CODEC_EFAILED; -- } else if (size > 0) { -- /* Only add packets containing more than the TOC */ -- opus_repacketizer_cat(opus_data->enc_packer, -- tmp_buf + out_pos, -- size); -- out_pos += size; -- tmp_bytes_left -= size; -- } -- in_pos += frame_size; -- } -+ opus = (struct opus_private*) codec->codec_data; - -- if (!opus_repacketizer_get_nb_frames(opus_data->enc_packer)) { -- /* Empty packet */ -- output->size = 0; -- output->type = PJMEDIA_FRAME_TYPE_NONE; -- output->timestamp = input->timestamp; -- } -+ err = opus_packet_parse(pkt, pkt_size, &toc, raw_frames, size, &payload_offset); -+ if (err <= 0) { -+ PJ_LOG(4, (THIS_FILE, "Error parsing Opus packet: %s", opus_strerror(err))); -+ *frame_cnt = 0; -+ return opus_to_pjsip_error_code(err); -+ } - -- if (size) { -- size = opus_repacketizer_out(opus_data->enc_packer, -- output->buf, -- output_buf_len); -- if (size < 0) { -- PJ_LOG(4, (THIS_FILE, "Encode failed! (%d), out_size: %u", -- size, output_buf_len)); -- pj_mutex_unlock (opus_data->mutex); -- return PJMEDIA_CODEC_EFAILED; -- } -+ *frame_cnt = (unsigned)err; -+ samples_per_frame = opus_packet_get_samples_per_frame(pkt, opus->externalFs); -+ -+#if _TRACE_OPUS -+ PJ_LOG(4, (THIS_FILE, "Pkt info : bw -> %d , spf -> %d", opus_packet_get_bandwidth(pkt), samples_per_frame)); -+#endif -+ -+ for (i = 0; i < *frame_cnt; i++) { -+ frames[i].type = PJMEDIA_FRAME_TYPE_AUDIO; -+ frames[i].bit_info = (((unsigned)ts->u64 & 0xFFFF) << 16) | (((unsigned)pkt & 0xFF) << 8) | i; -+ frames[i].buf = pkt; -+ frames[i].size = pkt_size; -+ frames[i].timestamp.u64 = ts->u64 + i * samples_per_frame; -+#if _TRACE_OPUS -+ PJ_LOG(4, (THIS_FILE, "parsed %d of %d",frames[i].size, *frame_cnt)); -+#endif - } - -- output->size = (unsigned)size; -- output->type = PJMEDIA_FRAME_TYPE_AUDIO; -- output->timestamp = input->timestamp; -- -- pj_mutex_unlock (opus_data->mutex); - return PJ_SUCCESS; - } - -+static pj_status_t opus_codec_decode(pjmedia_codec *codec, const struct pjmedia_frame *input, unsigned output_buf_len, struct pjmedia_frame *output) { -+ struct opus_private *opus; -+ unsigned pkt_info, frm_info, frm_size; - --/* -- * Decode frame. -- */ --static pj_status_t codec_decode( pjmedia_codec *codec, -- const struct pjmedia_frame *input, -- unsigned output_buf_len, -- struct pjmedia_frame *output ) --{ -- struct opus_data *opus_data = (struct opus_data *)codec->codec_data; -- int decoded_samples; -- pjmedia_frame *inframe; -- int fec = 0; -+ PJ_ASSERT_RETURN(codec && input && output_buf_len && output, PJ_EINVAL); - -- pj_mutex_lock (opus_data->mutex); -+ opus = (struct opus_private*) codec->codec_data; - -- if (opus_data->dec_frame_index == -1) { -- /* First packet, buffer it. */ -- opus_data->dec_frame[0].type = input->type; -- opus_data->dec_frame[0].size = input->size; -- opus_data->dec_frame[0].timestamp = input->timestamp; -- pj_memcpy(opus_data->dec_frame[0].buf, input->buf, input->size); -- opus_data->dec_frame_index = 0; -- pj_mutex_unlock (opus_data->mutex); -- -- /* Return zero decoded bytes */ -- output->size = 0; -- output->type = PJMEDIA_FRAME_TYPE_NONE; -- output->timestamp = input->timestamp; -- -- return PJ_SUCCESS; -+ pkt_info = input->bit_info & 0xFFFFFF00; -+ frm_info = input->bit_info & 0xF; -+ if (opus->pkt_info != pkt_info || input->bit_info == 0) { -+ opus->pkt_info = pkt_info; -+ opus->dec_buf_sample_per_frame = opus_packet_get_samples_per_frame(input->buf, opus->externalFs); -+ /* We need to decode all the frames in the packet. */ -+ opus->dec_buf_size = opus_decode(opus->psDec, -+ (const unsigned char *) input->buf, -+ (opus_int32) input->size, -+ opus->dec_buf, -+ opus->dec_buf_max_size, -+ 0 /* decode FEC */); -+ if(opus->dec_buf_size <= 0){ -+ PJ_LOG(2, (THIS_FILE, "Failed to decode frame (err=%d)", opus->dec_buf_size)); -+ opus->dec_buf_size = 0; -+ } else { -+ opus->dec_buf_size = opus->dec_buf_size * opus->pcm_bytes_per_sample; -+ } - } - -- inframe = &opus_data->dec_frame[opus_data->dec_frame_index]; -+ /* We have this packet decoded now (either was previously in the buffer or was just added to buffer). */ -+ if (opus->dec_buf_size == 0) { -+ /* The decoding was a failure. */ -+ output->size = 0; -+ } else { -+ frm_size = opus->dec_buf_sample_per_frame * opus->pcm_bytes_per_sample; -+#if _TRACE_OPUS -+ PJ_LOG(4, (THIS_FILE, "Decode : copy from big buffer %d to %d", output_buf_len, frm_size)); -+#endif -+ if(output_buf_len < frm_size){ -+ return PJ_ETOOSMALL; -+ } -+ /* Copy the decoded frame from the buffer. */ -+ pj_memcpy(output->buf, ((opus_int16*)opus->dec_buf) + (frm_info * frm_size), frm_size); -+ output->size = frm_size; -+ } - -- if (inframe->type != PJMEDIA_FRAME_TYPE_AUDIO) { -- /* Update current frame index */ -- opus_data->dec_frame_index++; -- if (opus_data->dec_frame_index > 1) -- opus_data->dec_frame_index = 0; -- /* Copy original input buffer to current indexed frame */ -- inframe = &opus_data->dec_frame[opus_data->dec_frame_index]; -- inframe->type = input->type; -- inframe->size = input->size; -- inframe->timestamp = input->timestamp; -- pj_memcpy(inframe->buf, input->buf, input->size); -- fec = 1; -- } -+ if (output->size == 0) { -+ output->type = PJMEDIA_FRAME_TYPE_NONE; -+ output->buf = NULL; -+ return PJMEDIA_CODEC_EFAILED; -+ } - -- decoded_samples = opus_decode(opus_data->dec, -- inframe->type==PJMEDIA_FRAME_TYPE_AUDIO ? -- inframe->buf : NULL, -- inframe->type==PJMEDIA_FRAME_TYPE_AUDIO ? -- inframe->size : 0, -- (opus_int16*)output->buf, -- output->size / (sizeof(opus_int16) * -- opus_data->cfg.channel_cnt), -- fec); -- output->timestamp = inframe->timestamp; -- -- if (inframe->type == PJMEDIA_FRAME_TYPE_AUDIO) { -- /* Mark current indexed frame as invalid */ -- inframe->type = PJMEDIA_FRAME_TYPE_NONE; -- /* Update current frame index */ -- opus_data->dec_frame_index++; -- if (opus_data->dec_frame_index > 1) -- opus_data->dec_frame_index = 0; -- /* Copy original input buffer to current indexed frame */ -- inframe = &opus_data->dec_frame[opus_data->dec_frame_index]; -- inframe->type = input->type; -- inframe->size = input->size; -- inframe->timestamp = input->timestamp; -- pj_memcpy(inframe->buf, input->buf, input->size); -- } -+ output->type = PJMEDIA_FRAME_TYPE_AUDIO; -+ output->timestamp = input->timestamp; - -- if (decoded_samples < 0) { -- PJ_LOG(4, (THIS_FILE, "Decode failed!")); -- pj_mutex_unlock (opus_data->mutex); -- return PJMEDIA_CODEC_EFAILED; -- } -- -- output->size = decoded_samples * sizeof(opus_int16) * -- opus_data->cfg.channel_cnt; -- output->type = PJMEDIA_FRAME_TYPE_AUDIO; -- -- pj_mutex_unlock (opus_data->mutex); -- return PJ_SUCCESS; -+#if _TRACE_OPUS -+ PJ_LOG(4, (THIS_FILE, "Decoded %d to %d with max %d", input->size, output->size, output_buf_len)); -+#endif -+ return PJ_SUCCESS; - } - -- - /* - * Recover lost frame. - */ --static pj_status_t codec_recover( pjmedia_codec *codec, -- unsigned output_buf_len, -- struct pjmedia_frame *output ) --{ -- struct opus_data *opus_data = (struct opus_data *)codec->codec_data; -- int decoded_samples; -- pjmedia_frame *inframe; -+static pj_status_t opus_codec_recover(pjmedia_codec *codec, unsigned output_buf_len, struct pjmedia_frame *output) { -+ struct opus_private *opus; -+ int ret = 0; -+ int frame_size; - -- pj_mutex_lock (opus_data->mutex); -+ PJ_ASSERT_RETURN(output, PJ_EINVAL); -+ opus = (struct opus_private*) codec->codec_data; - -- if (opus_data->dec_frame_index == -1) { -- /* Recover the first packet? Don't think so, fill it with zeroes. */ -- pj_uint16_t samples_per_frame; -- samples_per_frame = (opus_data->cfg.sample_rate * -- opus_data->ptime) / 1000; -- output->type = PJMEDIA_FRAME_TYPE_AUDIO; -- output->size = samples_per_frame << 1; -- pjmedia_zero_samples((pj_int16_t*)output->buf, samples_per_frame); -- pj_mutex_unlock (opus_data->mutex); -+ frame_size = output_buf_len / opus->pcm_bytes_per_sample; -+ /* Decode */ -+ ret = opus_decode(opus->psDec, (const unsigned char *) NULL, 0, output->buf, frame_size, 0); -+ if (ret < 0) { -+ PJ_LOG(1, (THIS_FILE, "Failed to recover opus frame %d", ret)); -+ return PJ_EINVAL; -+ } else if (ret == 0) { -+#if _TRACE_OPUS -+ PJ_LOG(4, (THIS_FILE, "Empty frame recovered %d", ret)); -+#endif -+ output->type = PJMEDIA_FRAME_TYPE_NONE; -+ output->buf = NULL; -+ output->size = 0; -+ } else { -+#if _TRACE_OPUS -+ PJ_LOG(4, (THIS_FILE, "Frame recovered %d", ret)); -+#endif -+ output->size = ret * opus->pcm_bytes_per_sample; -+ output->type = PJMEDIA_FRAME_TYPE_AUDIO; -+ } - -- return PJ_SUCCESS; -- } -- -- inframe = &opus_data->dec_frame[opus_data->dec_frame_index]; -- decoded_samples = opus_decode(opus_data->dec, -- inframe->type==PJMEDIA_FRAME_TYPE_AUDIO ? -- inframe->buf : NULL, -- inframe->type==PJMEDIA_FRAME_TYPE_AUDIO ? -- inframe->size : 0, -- (opus_int16*)output->buf, -- output->size / (sizeof(opus_int16) * -- opus_data->cfg.channel_cnt), -- 0); -- -- /* Mark current indexed frame as invalid */ -- inframe->type = PJMEDIA_FRAME_TYPE_NONE; -- -- /* Update current frame index */ -- opus_data->dec_frame_index++; -- if (opus_data->dec_frame_index > 1) -- opus_data->dec_frame_index = 0; -- /* Mark current indexed frame as invalid */ -- inframe = &opus_data->dec_frame[opus_data->dec_frame_index]; -- inframe->type = PJMEDIA_FRAME_TYPE_NONE; -- -- if (decoded_samples < 0) { -- PJ_LOG(4, (THIS_FILE, "Recover failed!")); -- pj_mutex_unlock (opus_data->mutex); -- return PJMEDIA_CODEC_EFAILED; -- } -- -- output->size = decoded_samples * sizeof(opus_int16) * -- opus_data->cfg.channel_cnt; -- output->type = PJMEDIA_FRAME_TYPE_AUDIO; -- output->timestamp = inframe->timestamp; -- -- pj_mutex_unlock (opus_data->mutex); -- return PJ_SUCCESS; -+ return PJ_SUCCESS; - } - --#if defined(_MSC_VER) --# pragma comment(lib, "libopus.a") - #endif -- -- --#endif /* PJMEDIA_HAS_OPUS_CODEC */ -Index: pjmedia/src/pjmedia-videodev/dshow_dev.c -=================================================================== ---- pjmedia/src/pjmedia-videodev/dshow_dev.c (revision 5249) -+++ pjmedia/src/pjmedia-videodev/dshow_dev.c (working copy) -@@ -20,30 +20,24 @@ - #include - #include - #include --#include - - - #if defined(PJMEDIA_HAS_VIDEO) && PJMEDIA_HAS_VIDEO != 0 && \ - defined(PJMEDIA_VIDEO_DEV_HAS_DSHOW) && PJMEDIA_VIDEO_DEV_HAS_DSHOW != 0 - -- --#ifdef _MSC_VER --# pragma warning(push, 3) --#endif -- - #include - #define COBJMACROS - #include - #include -+#include - --#ifdef _MSC_VER --# pragma warning(pop) -+#ifndef DIBSIZE -+# define WIDTHBYTES(BTIS) ((DWORD)(((BTIS)+31) & (~31)) / 8) -+# define DIBWIDTHBYTES(BI) (DWORD)(BI).biBitCount) * (DWORD)WIDTHBYTES((DWORD)(BI).biWidth -+# define _DIBSIZE(BI) (DIBWIDTHBYTES(BI) * (DWORD)(BI).biHeight) -+# define DIBSIZE(BI) ((BI).biHeight < 0 ? (-1)*(_DIBSIZE(BI)) : _DIBSIZE(BI)) - #endif - --#pragma comment(lib, "Strmiids.lib") --#pragma comment(lib, "Rpcrt4.lib") --#pragma comment(lib, "Quartz.lib") -- - #define THIS_FILE "dshow_dev.c" - #define DEFAULT_CLOCK_RATE 90000 - #define DEFAULT_WIDTH 640 -@@ -57,10 +51,6 @@ - typedef struct NullRenderer NullRenderer; - IBaseFilter* NullRenderer_Create(input_callback input_cb, - void *user_data); --typedef struct SourceFilter SourceFilter; --IBaseFilter* SourceFilter_Create(SourceFilter **pSrc); --HRESULT SourceFilter_Deliver(SourceFilter *src, void *buf, long size); --void SourceFilter_SetMediaType(SourceFilter *src, AM_MEDIA_TYPE *pmt); - - typedef struct dshow_fmt_info - { -@@ -121,7 +111,6 @@ - { - IFilterGraph *filter_graph; - IMediaFilter *media_filter; -- SourceFilter *csource_filter; - IBaseFilter *source_filter; - IBaseFilter *rend_filter; - AM_MEDIA_TYPE *mediatype; -@@ -160,8 +149,6 @@ - pjmedia_vid_dev_cap cap, - const void *value); - static pj_status_t dshow_stream_start(pjmedia_vid_dev_stream *strm); --static pj_status_t dshow_stream_put_frame(pjmedia_vid_dev_stream *strm, -- const pjmedia_frame *frame); - static pj_status_t dshow_stream_stop(pjmedia_vid_dev_stream *strm); - static pj_status_t dshow_stream_destroy(pjmedia_vid_dev_stream *strm); - -@@ -184,7 +171,7 @@ - &dshow_stream_set_cap, - &dshow_stream_start, - NULL, -- &dshow_stream_put_frame, -+ NULL, - &dshow_stream_stop, - &dshow_stream_destroy - }; -@@ -213,19 +200,12 @@ - /* API: init factory */ - static pj_status_t dshow_factory_init(pjmedia_vid_dev_factory *f) - { -- HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); -- if (hr == RPC_E_CHANGED_MODE) { -- /* When using apartment mode, Dshow object would not be accessible from -- * other thread. Take this into consideration when implementing native -- * renderer using Dshow. -- */ -- hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); -- if (FAILED(hr)) { -- PJ_LOG(4,(THIS_FILE, "Failed initializing DShow: " -- "COM library already initialized with " -- "incompatible concurrency model")); -- return PJMEDIA_EVID_INIT; -- } -+ HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); -+ if (hr == RPC_E_CHANGED_MODE) { -+ PJ_LOG(4,(THIS_FILE, "Failed initializing DShow: " -+ "COM library already initialized with " -+ "incompatible concurrency model")); -+ return PJMEDIA_EVID_INIT; - } - - return dshow_factory_refresh(f); -@@ -459,14 +439,22 @@ - if (SUCCEEDED(hr) && var_name.bstrVal) { - WCHAR *wszDisplayName = NULL; - IBaseFilter *filter; -+ pj_ssize_t len; - - ddi = &df->dev_info[df->dev_count++]; - pj_bzero(ddi, sizeof(*ddi)); -- pj_unicode_to_ansi(var_name.bstrVal, -- wcslen(var_name.bstrVal), -- ddi->info.name, -- sizeof(ddi->info.name)); - -+ len = wcslen(var_name.bstrVal), -+ len = WideCharToMultiByte(CP_ACP, -+ 0, -+ var_name.bstrVal, -+ (int)len, -+ ddi->info.name, -+ sizeof(ddi->info.name), -+ NULL, -+ NULL); -+ ddi->info.name[len] = '\0'; -+ - hr = IMoniker_GetDisplayName(moniker, NULL, NULL, - &wszDisplayName); - if (hr == S_OK && wszDisplayName) { -@@ -502,26 +490,6 @@ - ICreateDevEnum_Release(dev_enum); - } - --#if HAS_VMR -- ddi = &df->dev_info[df->dev_count++]; -- pj_bzero(ddi, sizeof(*ddi)); -- pj_ansi_strncpy(ddi->info.name, "Video Mixing Renderer", -- sizeof(ddi->info.name)); -- ddi->info.name[sizeof(ddi->info.name)-1] = '\0'; -- pj_ansi_strncpy(ddi->info.driver, "dshow", sizeof(ddi->info.driver)); -- ddi->info.driver[sizeof(ddi->info.driver)-1] = '\0'; -- ddi->info.dir = PJMEDIA_DIR_RENDER; -- ddi->info.has_callback = PJ_FALSE; -- ddi->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT; --// TODO: --// ddi->info.caps |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW; -- -- ddi->info.fmt_cnt = 1; -- pjmedia_format_init_video(&ddi->info.fmt[0], dshow_fmts[0].pjmedia_format, -- DEFAULT_WIDTH, DEFAULT_HEIGHT, -- DEFAULT_FPS, 1); --#endif -- - PJ_LOG(4, (THIS_FILE, "DShow has %d devices:", - df->dev_count)); - for (c = 0; c < df->dev_count; ++c) { -@@ -574,10 +542,6 @@ - param->dir = PJMEDIA_DIR_CAPTURE; - param->cap_id = index; - param->rend_id = PJMEDIA_VID_INVALID_DEV; -- } else if (di->info.dir & PJMEDIA_DIR_RENDER) { -- param->dir = PJMEDIA_DIR_RENDER; -- param->rend_id = index; -- param->cap_id = PJMEDIA_VID_INVALID_DEV; - } else { - return PJMEDIA_EVID_INVDEV; - } -@@ -645,26 +609,6 @@ - (*strm->vid_cb.capture_cb)(&strm->base, strm->user_data, &frame); - } - --/* API: Put frame from stream */ --static pj_status_t dshow_stream_put_frame(pjmedia_vid_dev_stream *strm, -- const pjmedia_frame *frame) --{ -- struct dshow_stream *stream = (struct dshow_stream*)strm; -- HRESULT hr; -- -- if (stream->quit_flag) { -- stream->rend_thread_exited = PJ_TRUE; -- return PJ_SUCCESS; -- } -- -- hr = SourceFilter_Deliver(stream->dgraph.csource_filter, -- frame->buf, (long)frame->size); -- if (FAILED(hr)) -- return hr; -- -- return PJ_SUCCESS; --} -- - static dshow_fmt_info* get_dshow_format_info(pjmedia_format_id id) - { - unsigned i; -@@ -689,16 +633,24 @@ - IEnumPins *pEnum; - IPin *srcpin = NULL; - IPin *sinkpin = NULL; -- AM_MEDIA_TYPE *mediatype= NULL, mtype; -+ AM_MEDIA_TYPE *mediatype = NULL; - VIDEOINFOHEADER *video_info, *vi = NULL; - pjmedia_video_format_detail *vfd; - const pjmedia_video_format_info *vfi; - -+ PJ_ASSERT_RETURN(dir == PJMEDIA_DIR_CAPTURE, PJ_EINVAL); -+ - vfi = pjmedia_get_video_format_info(pjmedia_video_format_mgr_instance(), - strm->param.fmt.id); - if (!vfi) - return PJMEDIA_EVID_BADFORMAT; - -+ hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); -+ if (FAILED(hr)) { -+ PJ_LOG(4,(THIS_FILE, "Error: CoInitializeEx")); -+ goto on_error; -+ } -+ - hr = CoCreateInstance(&CLSID_FilterGraph, NULL, CLSCTX_INPROC, - &IID_IFilterGraph, (LPVOID *)&graph->filter_graph); - if (FAILED(hr)) { -@@ -711,14 +663,10 @@ - goto on_error; - } - -- if (dir == PJMEDIA_DIR_CAPTURE) { - hr = get_cap_device(df, id, &graph->source_filter); - if (FAILED(hr)) { - goto on_error; - } -- } else { -- graph->source_filter = SourceFilter_Create(&graph->csource_filter); -- } - - hr = IFilterGraph_AddFilter(graph->filter_graph, graph->source_filter, - L"capture"); -@@ -726,16 +674,7 @@ - goto on_error; - } - -- if (dir == PJMEDIA_DIR_CAPTURE) { -- graph->rend_filter = NullRenderer_Create(input_cb, strm); -- } else { -- hr = CoCreateInstance(&CLSID_VideoMixingRenderer, NULL, -- CLSCTX_INPROC, &IID_IBaseFilter, -- (LPVOID *)&graph->rend_filter); -- if (FAILED (hr)) { -- goto on_error; -- } -- } -+ graph->rend_filter = NullRenderer_Create(input_cb, strm); - - IBaseFilter_EnumPins(graph->rend_filter, &pEnum); - if (SUCCEEDED(hr)) { -@@ -763,34 +702,6 @@ - (use_def_size? 0: vfd->size.h), &srcpin, NULL); - graph->mediatype = mediatype; - -- if (srcpin && dir == PJMEDIA_DIR_RENDER) { -- mediatype = graph->mediatype = &mtype; -- -- memset (mediatype, 0, sizeof(AM_MEDIA_TYPE)); -- mediatype->majortype = MEDIATYPE_Video; -- mediatype->subtype = *(get_dshow_format_info(strm->param.fmt.id)-> -- dshow_format); -- mediatype->bFixedSizeSamples = TRUE; -- mediatype->bTemporalCompression = FALSE; -- -- vi = (VIDEOINFOHEADER *) -- CoTaskMemAlloc(sizeof(VIDEOINFOHEADER)); -- memset (vi, 0, sizeof(VIDEOINFOHEADER)); -- mediatype->formattype = FORMAT_VideoInfo; -- mediatype->cbFormat = sizeof(VIDEOINFOHEADER); -- mediatype->pbFormat = (BYTE *)vi; -- -- vi->rcSource.bottom = vfd->size.h; -- vi->rcSource.right = vfd->size.w; -- vi->rcTarget.bottom = vfd->size.h; -- vi->rcTarget.right = vfd->size.w; -- -- vi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); -- vi->bmiHeader.biPlanes = 1; -- vi->bmiHeader.biBitCount = vfi->bpp; -- vi->bmiHeader.biCompression = strm->param.fmt.id; -- } -- - if (!srcpin || !sinkpin || !mediatype) { - hr = VFW_E_TYPE_NOT_ACCEPTED; - goto on_error; -@@ -809,9 +720,6 @@ - } - video_info->bmiHeader.biSizeImage = DIBSIZE(video_info->bmiHeader); - mediatype->lSampleSize = DIBSIZE(video_info->bmiHeader); -- if (graph->csource_filter) -- SourceFilter_SetMediaType(graph->csource_filter, -- mediatype); - - hr = IFilterGraph_AddFilter(graph->filter_graph, - (IBaseFilter *)graph->rend_filter, -@@ -893,9 +801,9 @@ - pj_pool_t *pool; - struct dshow_stream *strm; - pj_status_t status; -+ const pjmedia_video_format_detail *vfd; - -- PJ_ASSERT_RETURN(param->dir == PJMEDIA_DIR_CAPTURE || -- param->dir == PJMEDIA_DIR_RENDER, PJ_EINVAL); -+ PJ_ASSERT_RETURN(param->dir == PJMEDIA_DIR_CAPTURE, PJ_EINVAL); - - if (!get_dshow_format_info(param->fmt.id)) - return PJMEDIA_EVID_BADFORMAT; -@@ -910,9 +818,6 @@ - pj_memcpy(&strm->vid_cb, cb, sizeof(*cb)); - strm->user_data = user_data; - -- if (param->dir & PJMEDIA_DIR_CAPTURE) { -- const pjmedia_video_format_detail *vfd; -- - /* Create capture stream here */ - status = create_filter_graph(PJMEDIA_DIR_CAPTURE, param->cap_id, - PJ_FALSE, PJ_FALSE, df, strm, -@@ -944,22 +849,7 @@ - - vfd = pjmedia_format_get_video_format_detail(¶m->fmt, PJ_TRUE); - strm->cap_ts_inc = PJMEDIA_SPF2(param->clock_rate, &vfd->fps, 1); -- } else if (param->dir & PJMEDIA_DIR_RENDER) { -- /* Create render stream here */ -- status = create_filter_graph(PJMEDIA_DIR_RENDER, param->rend_id, -- PJ_FALSE, PJ_FALSE, df, strm, -- &strm->dgraph); -- if (status != PJ_SUCCESS) -- goto on_error; -- } - -- /* Apply the remaining settings */ -- if (param->flags & PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW) { -- dshow_stream_set_cap(&strm->base, -- PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW, -- ¶m->window); -- } -- - /* Done */ - strm->base.op = &stream_op; - *p_vid_strm = &strm->base; -Index: pjmedia/src/pjmedia-videodev/videodev.c -=================================================================== ---- pjmedia/src/pjmedia-videodev/videodev.c (revision 5249) -+++ pjmedia/src/pjmedia-videodev/videodev.c (working copy) -@@ -77,20 +77,12 @@ - pjmedia_vid_dev_factory* pjmedia_cbar_factory(pj_pool_factory *pf); - #endif - --#if PJMEDIA_VIDEO_DEV_HAS_SDL --pjmedia_vid_dev_factory* pjmedia_sdl_factory(pj_pool_factory *pf); --#endif -- --#if PJMEDIA_VIDEO_DEV_HAS_FFMPEG --pjmedia_vid_dev_factory* pjmedia_ffmpeg_factory(pj_pool_factory *pf); --#endif -- - #if PJMEDIA_VIDEO_DEV_HAS_V4L2 - pjmedia_vid_dev_factory* pjmedia_v4l2_factory(pj_pool_factory *pf); - #endif - --#if PJMEDIA_VIDEO_DEV_HAS_QT --pjmedia_vid_dev_factory* pjmedia_qt_factory(pj_pool_factory *pf); -+#if PJMEDIA_VIDEO_DEV_HAS_AVF -+pjmedia_vid_dev_factory* pjmedia_avf_factory(pj_pool_factory *pf); - #endif - - #if PJMEDIA_VIDEO_DEV_HAS_IOS -@@ -101,6 +93,14 @@ - pjmedia_vid_dev_factory* pjmedia_opengl_factory(pj_pool_factory *pf); - #endif - -+#if PJMEDIA_VIDEO_DEV_HAS_FB -+pjmedia_vid_dev_factory* pjmedia_fb_factory(pj_pool_factory *pf); -+#endif -+ -+#if PJMEDIA_VIDEO_DEV_HAS_NULL -+pjmedia_vid_dev_factory* pjmedia_null_factory(pj_pool_factory *pf); -+#endif -+ - #if PJMEDIA_VIDEO_DEV_HAS_ANDROID - pjmedia_vid_dev_factory* pjmedia_and_factory(pj_pool_factory *pf); - #endif -@@ -382,8 +382,8 @@ - #if PJMEDIA_VIDEO_DEV_HAS_V4L2 - vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_v4l2_factory; - #endif --#if PJMEDIA_VIDEO_DEV_HAS_QT -- vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_qt_factory; -+#if PJMEDIA_VIDEO_DEV_HAS_AVF -+ vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_avf_factory; - #endif - #if PJMEDIA_VIDEO_DEV_HAS_OPENGL - vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_opengl_factory; -@@ -394,21 +394,15 @@ - #if PJMEDIA_VIDEO_DEV_HAS_DSHOW - vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_dshow_factory; - #endif --#if PJMEDIA_VIDEO_DEV_HAS_FFMPEG -- vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_ffmpeg_factory; -+#if PJMEDIA_VIDEO_DEV_HAS_CBAR_SRC -+ vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_cbar_factory; - #endif --#if PJMEDIA_VIDEO_DEV_HAS_SDL -- vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_sdl_factory; -+#if PJMEDIA_VIDEO_DEV_HAS_FB -+ vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_fb_factory; - #endif --#if PJMEDIA_VIDEO_DEV_HAS_ANDROID -- vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_and_factory; -+#if PJMEDIA_VIDEO_DEV_HAS_NULL -+ vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_null_factory; - #endif --#if PJMEDIA_VIDEO_DEV_HAS_CBAR_SRC -- /* Better put colorbar at the last, so the default capturer will be -- * a real capturer, if any. -- */ -- vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_cbar_factory; --#endif - - /* Initialize each factory and build the device ID list */ - for (i=0; istate; -+ -+ if (prev != state) { -+ ice_st->state = state; -+ if (ice_st->cb.on_ice_state) -+ (*ice_st->cb.on_ice_state)(ice_st, prev, state); -+ } -+} -+ -+ - /* Validate configuration */ - static pj_status_t pj_ice_strans_cfg_check_valid(const pj_ice_strans_cfg *cfg) - { -@@ -638,7 +651,7 @@ - pj_pool_calloc(pool, comp_cnt, sizeof(pj_ice_strans_comp*)); - - /* Move state to candidate gathering */ -- ice_st->state = PJ_ICE_STRANS_STATE_INIT; -+ set_ice_state(ice_st, PJ_ICE_STRANS_STATE_INIT); - - /* Acquire initialization mutex to prevent callback to be - * called before we finish initialization. -@@ -795,7 +808,7 @@ - - /* All candidates have been gathered */ - ice_st->cb_called = PJ_TRUE; -- ice_st->state = PJ_ICE_STRANS_STATE_READY; -+ set_ice_state(ice_st, PJ_ICE_STRANS_STATE_READY); - if (ice_st->cb.on_ice_complete) - (*ice_st->cb.on_ice_complete)(ice_st, PJ_ICE_STRANS_OP_INIT, - PJ_SUCCESS); -@@ -948,7 +961,7 @@ - } - - /* ICE session is ready for negotiation */ -- ice_st->state = PJ_ICE_STRANS_STATE_SESS_READY; -+ set_ice_state(ice_st, PJ_ICE_STRANS_STATE_SESS_READY); - - return PJ_SUCCESS; - -@@ -1171,7 +1184,7 @@ - return status; - } - -- ice_st->state = PJ_ICE_STRANS_STATE_NEGO; -+ set_ice_state(ice_st, PJ_ICE_STRANS_STATE_NEGO); - return status; - } - -@@ -1192,6 +1205,25 @@ - } - - /* -+ * Get ICE session. -+ */ -+PJ_DEF(pj_ice_sess*) -+pj_ice_strans_get_session(const pj_ice_strans *ice_st) -+{ -+ return ice_st->ice; -+} -+ -+/* -+ * Get ICE start time. -+ */ -+PJ_DEF(pj_time_val) -+pj_ice_strans_get_start_time(const pj_ice_strans *ice_st) -+{ -+ -+ return ice_st->start_time; -+} -+ -+/* - * Stop ICE! - */ - PJ_DEF(pj_status_t) pj_ice_strans_stop_ice(pj_ice_strans *ice_st) -@@ -1209,7 +1241,7 @@ - ice_st->ice = NULL; - } - -- ice_st->state = PJ_ICE_STRANS_STATE_INIT; -+ set_ice_state(ice_st, PJ_ICE_STRANS_STATE_INIT); - - pj_grp_lock_release(ice_st->grp_lock); - -@@ -1387,8 +1419,8 @@ - } - } - -- ice_st->state = (status==PJ_SUCCESS) ? PJ_ICE_STRANS_STATE_RUNNING : -- PJ_ICE_STRANS_STATE_FAILED; -+ set_ice_state(ice_st, (status==PJ_SUCCESS) ? PJ_ICE_STRANS_STATE_RUNNING : -+ PJ_ICE_STRANS_STATE_FAILED); - - pj_log_push_indent(); - (*ice_st->cb.on_ice_complete)(ice_st, PJ_ICE_STRANS_OP_NEGOTIATION, -Index: pjsip/include/pjsip/sip_msg.h -=================================================================== ---- pjsip/include/pjsip/sip_msg.h (revision 5249) -+++ pjsip/include/pjsip/sip_msg.h (working copy) -@@ -673,6 +673,21 @@ - }; - - /** -+ * General purpose function to print a SIP message body. -+ * Uses the appropriate internal functions to print the string representation -+ * of a SIP message body. It sets the output buffer to a statically allocated -+ * buffer, so the caller is responsible to copy it. -+ * -+ * @param msg_body The message body. -+ * @param buf Pointer to get the result buffer (statically allocated). -+ * @param size The size of the buffer. -+ * -+ * @return The length copied to the buffer, or -1. -+ */ -+PJ_DECL(int) pjsip_print_body( pjsip_msg_body *msg_body, -+ char **buf, int *len); -+ -+/** - * General purpose function to textual data in a SIP body. Attach this function - * in a SIP message body only if the data in pjsip_msg_body is a textual - * message ready to be embedded in a SIP message. If the data in the message -@@ -892,6 +907,20 @@ - PJ_DECL(void*) pjsip_msg_find_remove_hdr( pjsip_msg *msg, - pjsip_hdr_e hdr, void *start); - -+/** -+ * Find and remove a header in the message. -+ * -+ * @param msg The message. -+ * @param name The header name to find. -+ * @param start The first header field where the search should begin, -+ * or NULL to search from the first header in the message. -+ * -+ * @return The header field, or NULL if not found. -+ */ -+PJ_DECL(void*) pjsip_msg_find_remove_hdr_by_name( pjsip_msg *msg, -+ pj_str_t *name, -+ void *start); -+ - /** - * Add a header to the message, putting it last in the header list. - * -Index: pjsip/include/pjsip-simple/evsub.h -=================================================================== ---- pjsip/include/pjsip-simple/evsub.h (revision 5249) -+++ pjsip/include/pjsip-simple/evsub.h (working copy) -@@ -490,7 +490,17 @@ - PJ_DECL(void*) pjsip_evsub_get_mod_data( pjsip_evsub *sub, unsigned mod_id ); - - -+/* Update evbsub internal refresh_time with the given interval */ -+PJ_DECL(void) pjsip_evsub_update_expires( pjsip_evsub *sub, -+ pj_uint32_t interval ); - -+ -+/* Set the specified timer (UAC or UAS) to the specified time */ -+PJ_DECL(void) pjsip_evsub_set_timer( pjsip_evsub *sub, -+ int timer_id, -+ pj_int32_t seconds ); -+ -+ - PJ_END_DECL - - /** -Index: pjsip/src/pjsip/sip_msg.c -=================================================================== ---- pjsip/src/pjsip/sip_msg.c (revision 5249) -+++ pjsip/src/pjsip/sip_msg.c (working copy) -@@ -395,6 +395,17 @@ - return hdr; - } - -+PJ_DEF(void*) pjsip_msg_find_remove_hdr_by_name( pjsip_msg *msg, -+ pj_str_t *name, -+ void *start) -+{ -+ pjsip_hdr *hdr = (pjsip_hdr*) pjsip_msg_find_hdr_by_name(msg, name, start); -+ if (hdr) { -+ pj_list_erase(hdr); -+ } -+ return hdr; -+} -+ - PJ_DEF(pj_ssize_t) pjsip_msg_print( const pjsip_msg *msg, - char *buf, pj_size_t size) - { -@@ -2133,6 +2144,20 @@ - /* - * Message body manipulations. - */ -+PJ_DEF(int) pjsip_print_body(pjsip_msg_body *msg_body, char **buf, int *len) -+{ -+ static char s_buf[PJSIP_MAX_PKT_LEN]; -+ int res; -+ -+ res = (*msg_body->print_body)(msg_body, s_buf, PJSIP_MAX_PKT_LEN); -+ if (res < 0) { -+ return -1; -+ } -+ *buf = s_buf; -+ *len = res; -+ return 0; -+} -+ - PJ_DEF(int) pjsip_print_text_body(pjsip_msg_body *msg_body, char *buf, pj_size_t size) - { - if (size < msg_body->len) -Index: pjsip/src/pjsip-simple/evsub.c -=================================================================== ---- pjsip/src/pjsip-simple/evsub.c (revision 5249) -+++ pjsip/src/pjsip-simple/evsub.c (working copy) -@@ -492,7 +492,12 @@ - sub->refresh_time.sec += interval; - } - -+PJ_DEF(void) pjsip_evsub_update_expires( pjsip_evsub *sub, pj_uint32_t interval ) -+{ -+ update_expires(sub, interval); -+} - -+ - /* - * Schedule timer. - */ -@@ -526,7 +531,13 @@ - } - } - -+PJ_DEF(void) pjsip_evsub_set_timer( pjsip_evsub *sub, int timer_id, -+ pj_int32_t seconds) -+{ -+ set_timer(sub, timer_id, seconds); -+} - -+ - /* - * Destructor. - */ -Index: pjsip/src/pjsip-simple/evsub_msg.c -=================================================================== ---- pjsip/src/pjsip-simple/evsub_msg.c (revision 5249) -+++ pjsip/src/pjsip-simple/evsub_msg.c (working copy) -@@ -291,6 +291,44 @@ - } - - /* -+ * Parse Allow-Events header. -+ */ -+static pjsip_hdr* parse_hdr_allow_events(pjsip_parse_ctx *ctx) -+{ -+ pjsip_allow_events_hdr *allow_events = -+ pjsip_allow_events_hdr_create(ctx->pool); -+ const pjsip_parser_const_t *pc = pjsip_parser_const(); -+ pj_scanner *scanner = ctx->scanner; -+ -+ /* Some header fields allow empty elements in the value: -+ * Accept, Allow, Supported -+ */ -+ if (pj_scan_is_eof(scanner) || -+ *scanner->curptr == '\r' || *scanner->curptr == '\n') -+ { -+ goto end; -+ } -+ -+ pj_scan_get( scanner, &pc->pjsip_NOT_COMMA_OR_NEWLINE, -+ &allow_events->values[0]); -+ allow_events->count++; -+ -+ while (*scanner->curptr == ',') { -+ pj_scan_get_char(scanner); -+ pj_scan_get( scanner, &pc->pjsip_NOT_COMMA_OR_NEWLINE, -+ &allow_events->values[allow_events->count]); -+ allow_events->count++; -+ -+ if (allow_events->count >= PJSIP_MAX_ALLOW_EVENTS) -+ break; -+ } -+ -+end: -+ pjsip_parse_end_hdr_imp(scanner); -+ return (pjsip_hdr*)allow_events; -+} -+ -+/* - * Register header parsers. - */ - PJ_DEF(void) pjsip_evsub_init_parser(void) -@@ -300,5 +338,8 @@ - - pjsip_register_hdr_parser( "Subscription-State", NULL, - &parse_hdr_sub_state); -+ -+ pjsip_register_hdr_parser( "Allow-Events", NULL, -+ &parse_hdr_allow_events); - } - -Index: pjsip/src/pjsip-ua/sip_inv.c -=================================================================== ---- pjsip/src/pjsip-ua/sip_inv.c (revision 5249) -+++ pjsip/src/pjsip-ua/sip_inv.c (working copy) -@@ -1935,6 +1935,19 @@ - return PJMEDIA_SDP_EINSDP; - } - -+ /* Only accept SDP in INVITE, UPDATE and ACK requests, 18x (reliable) and 183 provisional responses -+ * and 200 final response. -+ */ -+ if (!(msg->type == PJSIP_REQUEST_MSG && msg->line.req.method.id == PJSIP_INVITE_METHOD) && -+ !(msg->type == PJSIP_REQUEST_MSG && msg->line.req.method.id == PJSIP_ACK_METHOD) && -+ !(msg->type == PJSIP_REQUEST_MSG && pjsip_method_cmp(&msg->line.req.method, &pjsip_update_method)==0) && -+ !(msg->type == PJSIP_RESPONSE_MSG && msg->line.status.code/10==18 && pjsip_100rel_is_reliable(rdata)) && -+ !(msg->type == PJSIP_RESPONSE_MSG && msg->line.status.code == 183) && -+ !(msg->type == PJSIP_RESPONSE_MSG && msg->line.status.code == 200)) { -+ PJ_LOG(4,(inv->obj_name, "ignored SDP body")); -+ return PJ_SUCCESS; -+ } -+ - /* Get/attach invite session's transaction data */ - tsx_inv_data = (struct tsx_inv_data*) tsx->mod_data[mod_inv.mod.id]; - if (tsx_inv_data == NULL) { -@@ -2144,6 +2157,11 @@ - { - status = pjmedia_sdp_neg_set_local_answer(inv->pool_prov, inv->neg, - local_sdp); -+ } else if (pjmedia_sdp_neg_get_state(inv->neg)== -+ PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER) -+ { -+ /* Go forward with our local offer */ -+ status = PJ_SUCCESS; - } else { - - /* Can not specify local SDP at this state. */ -@@ -3875,8 +3893,9 @@ - if (inv->state != PJSIP_INV_STATE_DISCONNECTED && - ((tsx->status_code == PJSIP_SC_CALL_TSX_DOES_NOT_EXIST && - tsx->method.id != PJSIP_CANCEL_METHOD) || -- tsx->status_code == PJSIP_SC_REQUEST_TIMEOUT || -- tsx->status_code == PJSIP_SC_TSX_TIMEOUT)) -+ (inv->state != PJSIP_INV_STATE_CONFIRMED && -+ (tsx->status_code == PJSIP_SC_TSX_TIMEOUT || -+ tsx->status_code == PJSIP_SC_TSX_TRANSPORT_ERROR)))) - { - pjsip_tx_data *bye; - pj_status_t status; -@@ -4715,6 +4734,7 @@ - * Handle strandled incoming CANCEL or CANCEL for re-INVITE - */ - inv_respond_incoming_cancel(inv, tsx, e); -+ - } - else if (tsx->method.id == PJSIP_INVITE_METHOD && - tsx->role == PJSIP_ROLE_UAS) diff --git a/deps/update_pjsip_210/README b/deps/update_pjsip_210/README new file mode 100644 index 00000000..5157787d --- /dev/null +++ b/deps/update_pjsip_210/README @@ -0,0 +1,9 @@ +This is experiemental work in progress, non-functional yet. + +1. ZSRTP does not compile on Mac + +2. undefined symbol: pjmedia_vid_stream_get_info + + +from sipsimple.core._core import * +ImportError: .local/lib/python3.6/site-packages/sipsimple/core/_core.cpython-36m-x86_64-linux-gnu.so: undefined symbol: pjmedia_vid_stream_get_info diff --git a/deps/update_pjsip_210/patches/pjsip_210.patch b/deps/update_pjsip_210/patches/pjsip_210.patch new file mode 100644 index 00000000..3d1b3e74 --- /dev/null +++ b/deps/update_pjsip_210/patches/pjsip_210.patch @@ -0,0 +1,7491 @@ +diff -ruN pjproject-2.10/base_rev pjsip/base_rev +--- pjproject-2.10/base_rev 1970-01-01 01:00:00.000000000 +0100 ++++ pjsip/base_rev 2021-02-07 00:21:58.163902742 +0100 +@@ -0,0 +1 @@ ++2.10 +diff -ruN pjproject-2.10/pjlib/src/pj/os_core_unix.c pjsip/pjlib/src/pj/os_core_unix.c +--- pjproject-2.10/pjlib/src/pj/os_core_unix.c 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/pjlib/src/pj/os_core_unix.c 2021-02-06 23:59:38.145916811 +0100 +@@ -37,6 +37,11 @@ + + #if defined(PJ_HAS_SEMAPHORE_H) && PJ_HAS_SEMAPHORE_H != 0 + # include ++# if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 ++# include ++# include ++# include ++# endif + #endif + + #include // getpid() +@@ -107,7 +112,11 @@ + #if defined(PJ_HAS_SEMAPHORE) && PJ_HAS_SEMAPHORE != 0 + struct pj_sem_t + { ++#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 ++ semaphore_t *sem; ++#else + sem_t *sem; ++#endif + char obj_name[PJ_MAX_OBJ_NAME]; + }; + #endif /* PJ_HAS_SEMAPHORE */ +@@ -1569,35 +1578,16 @@ + PJ_ASSERT_RETURN(sem, PJ_ENOMEM); + + #if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 +- /* MacOS X doesn't support anonymous semaphore */ + { +- char sem_name[PJ_GUID_MAX_LENGTH+1]; +- pj_str_t nam; +- +- /* We should use SEM_NAME_LEN, but this doesn't seem to be +- * declared anywhere? The value here is just from trial and error +- * to get the longest name supported. +- */ +-# define MAX_SEM_NAME_LEN 23 +- +- /* Create a unique name for the semaphore. */ +- if (PJ_GUID_STRING_LENGTH <= MAX_SEM_NAME_LEN) { +- nam.ptr = sem_name; +- pj_generate_unique_string(&nam); +- sem_name[nam.slen] = '\0'; +- } else { +- pj_create_random_string(sem_name, MAX_SEM_NAME_LEN); +- sem_name[MAX_SEM_NAME_LEN] = '\0'; +- } +- +- /* Create semaphore */ +- sem->sem = sem_open(sem_name, O_CREAT|O_EXCL, S_IRUSR|S_IWUSR, +- initial); +- if (sem->sem == SEM_FAILED) +- return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); +- +- /* And immediately release the name as we don't need it */ +- sem_unlink(sem_name); ++ kern_return_t err; ++ sem->sem = PJ_POOL_ALLOC_T(pool, semaphore_t); ++ err = semaphore_create(mach_task_self(), sem->sem, SYNC_POLICY_FIFO, initial); ++ if (err != KERN_SUCCESS) { ++ if (err == KERN_RESOURCE_SHORTAGE) ++ return PJ_RETURN_OS_ERROR(ENOMEM); ++ else ++ return PJ_RETURN_OS_ERROR(EINVAL); ++ } + } + #else + sem->sem = PJ_POOL_ALLOC_T(pool, sem_t); +@@ -1633,6 +1623,7 @@ + { + #if PJ_HAS_THREADS + int result; ++ int error; + + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(sem, PJ_EINVAL); +@@ -1640,6 +1631,20 @@ + PJ_LOG(6, (sem->obj_name, "Semaphore: thread %s is waiting", + pj_thread_this()->obj_name)); + ++#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 ++ { ++ do ++ result = semaphore_wait(*(sem->sem)); ++ while (result == KERN_ABORTED); ++ ++ if (result == KERN_SUCCESS) { ++ result = error = 0; ++ } else { ++ result = -1; ++ error = EINVAL; ++ } ++ } ++#else + result = sem_wait( sem->sem ); + + if (result == 0) { +@@ -1648,12 +1653,14 @@ + } else { + PJ_LOG(6, (sem->obj_name, "Semaphore: thread %s FAILED to acquire", + pj_thread_this()->obj_name)); ++ error = pj_get_native_os_error(); + } ++#endif + + if (result == 0) + return PJ_SUCCESS; + else +- return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); ++ return PJ_RETURN_OS_ERROR(error); + #else + pj_assert( sem == (pj_sem_t*) 1 ); + return PJ_SUCCESS; +@@ -1667,20 +1674,45 @@ + { + #if PJ_HAS_THREADS + int result; ++ int error; + + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(sem, PJ_EINVAL); + ++#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 ++ { ++ mach_timespec_t interval; ++ kern_return_t err; ++ ++ interval.tv_sec = 0; ++ interval.tv_nsec = 0; ++ ++ err = semaphore_timedwait(*(sem->sem), interval); ++ if (err == KERN_SUCCESS) { ++ result = error = 0; ++ } else if (err == KERN_OPERATION_TIMED_OUT) { ++ result = -1; ++ error = EAGAIN; ++ } else { ++ result = -1; ++ error = EINVAL; ++ } ++ } ++#else + result = sem_trywait( sem->sem ); + + if (result == 0) { + PJ_LOG(6, (sem->obj_name, "Semaphore acquired by thread %s", + pj_thread_this()->obj_name)); ++ } else { ++ error = pj_get_native_os_error(); + } ++#endif ++ + if (result == 0) + return PJ_SUCCESS; + else +- return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); ++ return PJ_RETURN_OS_ERROR(error); + #else + pj_assert( sem == (pj_sem_t*)1 ); + return PJ_SUCCESS; +@@ -1694,14 +1726,30 @@ + { + #if PJ_HAS_THREADS + int result; ++ int error; + PJ_LOG(6, (sem->obj_name, "Semaphore released by thread %s", + pj_thread_this()->obj_name)); ++#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 ++ { ++ kern_return_t err; ++ err = semaphore_signal(*(sem->sem)); ++ if (err == KERN_SUCCESS) { ++ result = error = 0; ++ } else { ++ result = -1; ++ error = EINVAL; ++ } ++ } ++#else + result = sem_post( sem->sem ); ++ if (result != 0) ++ error = pj_get_native_os_error(); ++#endif + + if (result == 0) + return PJ_SUCCESS; + else +- return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); ++ return PJ_RETURN_OS_ERROR(error); + #else + pj_assert( sem == (pj_sem_t*) 1); + return PJ_SUCCESS; +@@ -1715,6 +1763,7 @@ + { + #if PJ_HAS_THREADS + int result; ++ int error; + + PJ_CHECK_STACK(); + PJ_ASSERT_RETURN(sem, PJ_EINVAL); +@@ -1722,15 +1771,26 @@ + PJ_LOG(6, (sem->obj_name, "Semaphore destroyed by thread %s", + pj_thread_this()->obj_name)); + #if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 +- result = sem_close( sem->sem ); ++ { ++ kern_return_t err; ++ err = semaphore_destroy(mach_task_self(), *(sem->sem)); ++ if (err == KERN_SUCCESS) { ++ result = error = -1; ++ } else { ++ result = -1; ++ error = EINVAL; ++ } ++ } + #else + result = sem_destroy( sem->sem ); ++ if (result != 0) ++ error = pj_get_native_os_error(); + #endif + + if (result == 0) + return PJ_SUCCESS; + else +- return PJ_RETURN_OS_ERROR(pj_get_native_os_error()); ++ return PJ_RETURN_OS_ERROR(error); + #else + pj_assert( sem == (pj_sem_t*) 1 ); + return PJ_SUCCESS; +diff -ruN pjproject-2.10/pjmedia/include/pjmedia/event.h pjsip/pjmedia/include/pjmedia/event.h +--- pjproject-2.10/pjmedia/include/pjmedia/event.h 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/pjmedia/include/pjmedia/event.h 2021-02-06 16:57:17.374166159 +0100 +@@ -83,6 +83,11 @@ + PJMEDIA_EVENT_KEYFRAME_MISSING = PJMEDIA_FOURCC('I', 'F', 'R', 'M'), + + /** ++ * Remote video decoder asked for a keyframe. ++ */ ++ PJMEDIA_EVENT_KEYFRAME_REQUESTED = PJMEDIA_FOURCC('I', 'F', 'R', 'R'), ++ ++ /** + * Video orientation has been changed event. + */ + PJMEDIA_EVENT_ORIENT_CHANGED = PJMEDIA_FOURCC('O', 'R', 'N', 'T'), +diff -ruN pjproject-2.10/pjmedia/include/pjmedia/format.h pjsip/pjmedia/include/pjmedia/format.h +--- pjproject-2.10/pjmedia/include/pjmedia/format.h 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/pjmedia/include/pjmedia/format.h 2021-02-06 18:30:18.321176790 +0100 +@@ -97,6 +97,7 @@ + /** + * 32bit RGB with alpha channel + */ ++ PJMEDIA_FORMAT_ARGB = PJMEDIA_FORMAT_PACK('A', 'R', 'G', 'B'), + PJMEDIA_FORMAT_RGBA = PJMEDIA_FORMAT_PACK('R', 'G', 'B', 'A'), + PJMEDIA_FORMAT_BGRA = PJMEDIA_FORMAT_PACK('B', 'G', 'R', 'A'), + +diff -ruN pjproject-2.10/pjmedia/include/pjmedia/mixer_port.h pjsip/pjmedia/include/pjmedia/mixer_port.h +--- pjproject-2.10/pjmedia/include/pjmedia/mixer_port.h 1970-01-01 01:00:00.000000000 +0100 ++++ pjsip/pjmedia/include/pjmedia/mixer_port.h 2021-02-06 18:42:19.161906996 +0100 +@@ -0,0 +1,69 @@ ++/* ++ * Copyright (C) 2010 AG Projects ++ * ++ * 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_MIXER_PORT_H__ ++#define __PJMEDIA_MIXER_PORT_H__ ++ ++/** ++ * @file mixer_port.h ++ * @brief Mixer media port. ++ */ ++#include ++ ++ ++ ++/** ++ * @defgroup PJMEDIA_MIXER_PORT Mixer Port ++ * @ingroup PJMEDIA_PORT ++ * @brief The second simplest type of media port which forwards the frames it ++ * gets unchanged. ++ * @{ ++ */ ++ ++ ++PJ_BEGIN_DECL ++ ++ ++/** ++ * Create Mixer port. ++ * ++ * @param pool Pool to allocate memory. ++ * @param sampling_rate Sampling rate of the port. ++ * @param channel_count Number of channels. ++ * @param samples_per_frame Number of samples per frame. ++ * @param bits_per_sample Number of bits per sample. ++ * @param p_port Pointer to receive the port instance. ++ * ++ * @return PJ_SUCCESS on success. ++ */ ++PJ_DECL(pj_status_t) pjmedia_mixer_port_create(pj_pool_t *pool, ++ unsigned sampling_rate, ++ unsigned channel_count, ++ unsigned samples_per_frame, ++ unsigned bits_per_sample, ++ pjmedia_port **p_port); ++ ++ ++PJ_END_DECL ++ ++/** ++ * @} ++ */ ++ ++ ++#endif /* __PJMEDIA_MIXER_PORT_H__ */ +diff -ruN pjproject-2.10/pjmedia/include/pjmedia/rtcp.h pjsip/pjmedia/include/pjmedia/rtcp.h +--- pjproject-2.10/pjmedia/include/pjmedia/rtcp.h 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/pjmedia/include/pjmedia/rtcp.h 2021-02-06 18:32:46.933482520 +0100 +@@ -256,6 +256,8 @@ + + pjmedia_rtcp_stat stat; /**< Bidirectional stream stat. */ + ++ pj_bool_t keyframe_requested; /** Set to true when RTCP PLI is received */ ++ + #if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0) + /** + * Specify whether RTCP XR processing is enabled on this session. +@@ -462,6 +464,23 @@ + pj_size_t *length, + const pj_str_t *reason); + ++/** ++ * Build an RTCP PLI packet. This packet can be appended to other RTCP ++ * packets, e.g: RTCP RR/SR, to compose a compound RTCP packet. ++ * ++ * @param session The RTCP session. ++ * @param buf The buffer to receive RTCP PLI packet. ++ * @param length On input, it will contain the buffer length. ++ * On output, it will contain the generated RTCP PLI ++ * packet length. ++ * ++ * @return PJ_SUCCESS on success. ++ */ ++PJ_DECL(pj_status_t) pjmedia_rtcp_build_rtcp_pli( ++ pjmedia_rtcp_session *session, ++ void *buf, ++ pj_size_t *length); ++ + + /** + * Call this function if RTCP XR needs to be enabled/disabled in the +diff -ruN pjproject-2.10/pjmedia/include/pjmedia/signatures.h pjsip/pjmedia/include/pjmedia/signatures.h +--- pjproject-2.10/pjmedia/include/pjmedia/signatures.h 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/pjmedia/include/pjmedia/signatures.h 2021-02-06 18:33:45.139162846 +0100 +@@ -153,6 +153,7 @@ + #define PJMEDIA_SIG_PORT_ECHO PJMEDIA_SIG_CLASS_PORT_AUD('E','C') + #define PJMEDIA_SIG_PORT_MEM_CAPTURE PJMEDIA_SIG_CLASS_PORT_AUD('M','C') + #define PJMEDIA_SIG_PORT_MEM_PLAYER PJMEDIA_SIG_CLASS_PORT_AUD('M','P') ++#define PJMEDIA_SIG_PORT_MIXER PJMEDIA_SIG_CLASS_PORT_AUD('M','X') + #define PJMEDIA_SIG_PORT_NULL PJMEDIA_SIG_CLASS_PORT_AUD('N','U') + #define PJMEDIA_SIG_PORT_RESAMPLE PJMEDIA_SIG_CLASS_PORT_AUD('R','E') + #define PJMEDIA_SIG_PORT_SPLIT_COMB PJMEDIA_SIG_CLASS_PORT_AUD('S','C') +diff -ruN pjproject-2.10/pjmedia/include/pjmedia/sound_port.h pjsip/pjmedia/include/pjmedia/sound_port.h +--- pjproject-2.10/pjmedia/include/pjmedia/sound_port.h 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/pjmedia/include/pjmedia/sound_port.h 2021-02-06 18:34:38.880711750 +0100 +@@ -344,6 +344,16 @@ + + + /** ++ * Reset the EC state in the sound port. ++ * ++ * @param snd_port The sound device port. ++ * ++ * @return PJ_SUCCESS on success. ++ */ ++PJ_DECL(pj_status_t) pjmedia_snd_port_reset_ec_state(pjmedia_snd_port *snd_port); ++ ++ ++/** + * Connect a port to the sound device port. If the sound device port has a + * sound recorder device, then this will start periodic function call to + * the port's put_frame() function. If the sound device has a sound player +diff -ruN pjproject-2.10/pjmedia/include/pjmedia/transport_ice.h pjsip/pjmedia/include/pjmedia/transport_ice.h +--- pjproject-2.10/pjmedia/include/pjmedia/transport_ice.h 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/pjmedia/include/pjmedia/transport_ice.h 2021-02-06 17:09:28.151078775 +0100 +@@ -74,6 +74,28 @@ + pj_status_t status, + void *user_data); + ++ /** ++ * This callback will be called when ICE state changes. ++ * ++ * @param tp PJMEDIA ICE transport. ++ * @param prev Previous state. ++ * @param curr Current state. ++ */ ++ void (*on_ice_state)(pjmedia_transport *tp, ++ pj_ice_strans_state prev, ++ pj_ice_strans_state curr); ++ ++ /** ++ * This callback will be called when ICE is stopped. ++ * ++ * @param tp PJMEDIA ICE transport. ++ * @param reason Reason for stopping ICE. ++ * @param err Error code ++ */ ++ void (*on_ice_stop)(pjmedia_transport *tp, ++ char *reason, ++ pj_status_t err); ++ + } pjmedia_ice_cb; + + +@@ -237,6 +259,17 @@ + pjmedia_transport **p_tp); + + /** ++ * Return the ICE stream transport associated with this PJMEDIA transport ++ * ++ * @param tp Media transport instance. ++ * ++ * @return Pointer to the pj_ice_strans instance associated with this ++ * media transport. ++ */ ++PJ_DECL(pj_ice_strans*) pjmedia_ice_get_strans(pjmedia_transport *tp); ++ ++ ++/** + * Get the group lock for the ICE media transport. + * + * @param tp The ICE media transport. +diff -ruN pjproject-2.10/pjmedia/include/pjmedia/transport_zrtp.h pjsip/pjmedia/include/pjmedia/transport_zrtp.h +--- pjproject-2.10/pjmedia/include/pjmedia/transport_zrtp.h 1970-01-01 01:00:00.000000000 +0100 ++++ pjsip/pjmedia/include/pjmedia/transport_zrtp.h 2021-02-06 16:42:58.084103561 +0100 +@@ -0,0 +1,647 @@ ++/* $Id$ */ ++/* ++ Copyright (C) 2010 Werner Dittmann ++ ++ 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 3 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, see . ++*/ ++ ++#ifndef __PJMEDIA_TRANSPORT_ZRTP_H__ ++#define __PJMEDIA_TRANSPORT_ZRTP_H__ ++ ++/** ++ * @file transport_zrtp.h ++ * @brief ZRTP Media Transport Adapter ++ */ ++ ++/* transport.h includes types.h -> config.h -> config_auto.h */ ++#include ++ ++#include "../../third_party/zsrtp/zrtp/zrtp/libzrtpcpp/ZrtpCWrapper.h" ++ ++/** ++ * @defgroup PJMEDIA_TRANSPORT_ZRTP ZRTP Transport Adapter ++ * @brief This the ZRTP transport adapter. ++ * @{ ++ * ++ * PJMEDIA extension to support GNU ZRTP. ++ * ++ * ZRTP was developed by Phil Zimmermann and provides functions to ++ * negotiate keys and other necessary data (crypto data) to set-up ++ * the Secure RTP (SRTP) crypto context. Refer to Phil's ZRTP ++ * specification at his Zfone ++ * project site to get more detailed information about the ++ * capabilities of ZRTP. ++ * ++ * Short overview of the ZRTP implementation ++ * ++ * ZRTP is a specific protocol to negotiate encryption algorithms ++ * and the required key material. ZRTP uses a RTP session to ++ * exchange its protocol messages. Thus ZRTP is independent of any ++ * signaling protocol like SIP, XMPP and alike. ++ * ++ * A complete GNU ZRTP implementation consists of two parts, the ++ * GNU ZRTP core and some specific code that binds the GNU ZRTP core to ++ * the underlying RTP/SRTP stack and the operating system: ++ *
    ++ *
  • ++ * The GNU ZRTP core is independent of a specific RTP/SRTP ++ * stack and the operationg system and consists of the ZRTP ++ * protocol state engine, the ZRTP protocol messages, and the ++ * GNU ZRTP engine. The GNU ZRTP engine provides methods to ++ * setup ZRTP message and to analyze received ZRTP messages, ++ * to compute the crypto data required for SRTP, and to ++ * maintain the required hashes and HMAC. ++ *
  • ++ *
  • ++ * The second part of an implementation is specific ++ * glue code the binds the GNU ZRTP core to the ++ * actual RTP/SRTP implementation and other operating system ++ * specific services such as timers, mutexes. ++ *
  • ++ *
++ * ++ * The GNU ZRTP core uses callback methods (refer to ++ * zrtp_Callback) to access RTP/SRTP or operating specific methods, ++ * for example to send data via the RTP stack, to access ++ * timers, provide mutex handling, and to report events to the ++ * application. ++ * ++ * The PJMEDIA ZRTP transport ++ * ++ * ZRTP transport implements code that is specific to the pjmedia ++ * implementation. ZRTP transport also implements the specific code to ++ * provide the mutex and timeout handling to the GNU ZRTP ++ * core. Both, the mutex and the timeout handling, use the pjlib ++ * library to stay independent of the operating ++ * seystem. ++ * ++ * To perform its tasks ZRTP transport ++ *
    ++ *
  • implements the pjmedia transport functions and callbacks. ++ *
  • ++ *
  • implements the zrtp_Callbacks methods to provide ++ * access and other specific services (timer, mutex) to GNU ++ * ZRTP ++ *
  • ++ *
  • provides ZRTP specific methods that applications may use ++ * to control and setup GNU ZRTP ++ *
  • ++ *
  • can register and use an application specific callback ++ * class (refer to zrtp_UserCallbacks) ++ *
  • ++ *
++ * ++ * After instantiating a GNU ZRTP session (see below for a short ++ * example) applications may use the methods of ++ * ZRTP transport and the ZRTP engine to control and setup GNU ZRTP, ++ * for example enable or disable ZRTP processing or getting ZRTP status ++ * information. ++ * ++ * GNU ZRTP defines zrtp_UserCallback methods structure that an application ++ * may use and register with ZRTP transport. GNU ZRTP and ZRTP transport ++ * use the zrtp_UserCallback methods to report ZRTP events to the ++ * application. The application may display this information to ++ * the user or act otherwise. ++ * ++ * The following figure depicts the relationships between ++ * ZRTP transport, pjmedia RTP implementation, the GNU ZRTP core, ++ * SRTP and an application that provides zrtp_UserCallback methods. ++ * ++ @verbatim ++ +-----------+ ++ | | ++ | SRTP-ZRTP | ++ | | ++ +-----------+ ++ |C Wrapper | ++ +-----+-----+ ++ | ++ | uses ++ | ++ +-----------------+ +-------+--------+ +-+-----------------+ ++ | App (pjsua) | | | |C| | ++ | creates a | uses | transport_zrtp | uses | | GNU ZRTP | ++ | ZRTP transport +------+ implements +------+W| core | ++ | and implements | | zrtp_Callback | |r| implementation | ++ |zrtp_UserCallback| | | |a| (ZRtp et al) | ++ +-----------------+ +----------------+ |p| | ++ +-+-----------------+ ++ ++@endverbatim ++ * ++ * The following short code snippet shows how to use ZRTP transport ++ * ++ * @code ++ * ++ * #include ++ * ... ++ * // Create media transport ++ * status = pjmedia_transport_udp_create(med_endpt, NULL, local_port, ++ * 0, &transport); ++ * if (status != PJ_SUCCESS) ++ * return status; ++ * ++ * status = pjmedia_transport_zrtp_create(med_endpt, NULL, transport, ++ * &zrtp_tp); ++ * app_perror(THIS_FILE, "Error creating zrtp", status); ++ * transport = zrtp_tp; ++ * if (dir == PJMEDIA_DIR_ENCODING) ++ * pjmedia_transport_zrtp_initialize(transport, "testenc.zid", 1, NULL); ++ * else ++ * pjmedia_transport_zrtp_initialize(transport, "testdec.zid", 1, NULL); ++ * ... ++ * @endcode ++ * ++ */ ++ ++#define PJMEDIA_TRANSPORT_TYPE_ZRTP PJMEDIA_TRANSPORT_TYPE_USER+2 ++ ++PJ_BEGIN_DECL ++ ++/** ++ * ZRTP option. ++ */ ++typedef enum pjmedia_zrtp_use ++{ ++ /** When this flag is specified, ZRTP will be disabled. */ ++ PJMEDIA_NO_ZRTP = 1, ++ ++ /** When this flag is specified, PJSUA-LIB creates a ZRTP transport ++ * call calls back the applicaion for further process if callback is ++ * set. ++ */ ++ PJMEDIA_CREATE_ZRTP = 2 ++ ++} pjmedia_zrtp_use; ++ ++/** ++ * This structure specifies ZRTP transport specific info. This will fit ++ * into \a buffer field of pjmedia_transport_specific_info. ++ */ ++typedef struct pjmedia_zrtp_info ++{ ++ /** ++ * Specify whether the ZRTP transport is active for this session. ++ */ ++ pj_bool_t active; ++ ++ /** ++ * Specify the cipher being used. ++ */ ++ char cipher[128]; ++ ++} pjmedia_zrtp_info; ++ ++/** ++ * Application callback methods. ++ * ++ * The RTP stack specific part of GNU ZRTP uses these callback methods ++ * to report ZRTP events to the application. Thus the application that ++ * instantiates the RTP stack shall implement these methods and show these ++ * inforemation to the user. ++ * ++ * CAVEAT
++ * All user callback methods run in the context of the RTP thread. Thus ++ * it is of paramount importance to keep the execution time of the methods ++ * as short as possible. ++ * ++ * @author Werner Dittmann ++ */ ++typedef struct pjmedia_zrtp_cb ++{ ++ /** ++ * Inform user interface that security is active now. ++ * ++ * ZRTP calls this method if the sender and the receiver are ++ * in secure mode now. ++ * ++ * @param cipher ++ * Name and mode of cipher used to encrypt the SRTP stream ++ */ ++ void (*secure_on)(pjmedia_transport *tp, char* cipher); ++ ++ /** ++ * Inform user interface that security is not active any more. ++ * ++ * ZRTP calls this method if either the sender or the receiver ++ * left secure mode. ++ * ++ */ ++ void (*secure_off)(pjmedia_transport *tp); ++ ++ /** ++ * Show the Short Authentication String (SAS) on user interface. ++ * ++ * ZRTP calls this method to display the SAS and inform about the SAS ++ * verification status. The user interface shall enable a SAS verfication ++ * button (or similar UI element). The user shall click on this UI ++ * element after he/she confirmed the SAS code with the partner. ++ * ++ * @param sas ++ * The string containing the SAS. ++ * @param verified ++ * If verified is true then SAS was verified by both ++ * parties during a previous call, otherwise it is set to false. ++ */ ++ void (*show_sas)(pjmedia_transport *tp, char* sas, int32_t verified); ++ ++ /** ++ * Inform the user that ZRTP received "go clear" message from its peer. ++ * ++ * On receipt of a go clear message the user is requested to confirm ++ * a switch to unsecure (clear) modus. Until the user confirms ZRTP ++ * (and the underlying RTP) does not send any data. ++ * ++ */ ++ void (*confirm_go_clear)(pjmedia_transport *tp); ++ ++ /** ++ * Show some information to user. ++ * ++ * ZRTP calls this method to display some information to the user. ++ * Along with the message ZRTP provides a severity indicator that ++ * defines: Info, Warning, Error, and Alert. Refer to the ++ * MessageSeverity enum in ZrtpCodes.h. The ++ * UI may use this indicator to highlight messages or alike. ++ * ++ * @param sev ++ * Severity of the message. ++ * @param subCode ++ * The subcode identifying the reason. ++ */ ++ void (*show_message)(pjmedia_transport *tp, int32_t sev, int32_t subCode); ++ ++ /** ++ * ZRTP transport calls this if the negotiation failed. ++ * ++ * ZRTPQueue calls this method in case ZRTP negotiation failed. The ++ * parameters show the severity as well as some explanatory text. ++ * Refer to the MessageSeverity enum above. ++ * ++ * @param severity ++ * This defines the message's severity ++ * @param subCode ++ * The subcode identifying the reason. ++ */ ++ void (*negotiation_failed)(pjmedia_transport *tp, int32_t severity, int32_t subCode); ++ ++ /** ++ * ZRTP transport calls this method if the other side does not support ZRTP. ++ * ++ * If the other side does not answer the ZRTP Hello packets then ++ * ZRTP calls this method. ++ * ++ */ ++ void (*not_supported_by_other)(pjmedia_transport *tp); ++ ++ /** ++ * ZRTP transport calls this method to inform about a PBX enrollment request. ++ * ++ * Please refer to chapter 8.3 ff to get more details about PBX enrollment ++ * and SAS relay. ++ * ++ * @param info ++ * Give some information to the user about the PBX requesting an ++ * enrollment. ++ */ ++ void (*ask_enrollment)(pjmedia_transport *tp, int32_t info); ++ ++ /** ++ * ZRTP transport calls this method to inform about PBX enrollment result. ++ * ++ * Informs the use about the acceptance or denial of an PBX enrollment ++ * request ++ * ++ * @param info ++ * Give some information to the user about the result of an ++ * enrollment. ++ */ ++ void (*inform_enrollment)(pjmedia_transport *tp, int32_t info); ++ ++ /** ++ * ZRTP transport calls this method to request a SAS signature. ++ * ++ * After ZRTP core was able to compute the Short Authentication String ++ * (SAS) it calls this method. The client may now use an approriate ++ * method to sign the SAS. The client may use ++ * setSignatureData() of ZrtpQueue to store the signature ++ * data an enable signature transmission to the other peer. Refer ++ * to chapter 8.2 of ZRTP specification. ++ * ++ * @param sas ++ * The SAS string to sign. ++ * @see ZrtpQueue#setSignatureData ++ * ++ */ ++ void (*sign_sas)(pjmedia_transport *tp, uint8_t* sas); ++ ++ /** ++ * ZRTP transport calls this method to request a SAS signature check. ++ * ++ * After ZRTP received a SAS signature in one of the Confirm packets it ++ * call this method. The client may use getSignatureLength() ++ * and getSignatureData()of ZrtpQueue to get the signature ++ * data and perform the signature check. Refer to chapter 8.2 of ZRTP ++ * specification. ++ * ++ * If the signature check fails the client may return false to ZRTP. In ++ * this case ZRTP signals an error to the other peer and terminates ++ * the ZRTP handshake. ++ * ++ * @param sas ++ * The SAS string that was signed by the other peer. ++ * @return ++ * true if the signature was ok, false otherwise. ++ * ++ */ ++ int32_t (*check_sas_signature)(pjmedia_transport *tp, uint8_t* sas); ++} pjmedia_zrtp_cb; ++ ++ ++/** ++ * Create the transport adapter, specifying the underlying transport to be ++ * used to send and receive RTP/RTCP packets. ++ * ++ * @param endpt The media endpoint. ++ * @param timer_heap The heap where timers will be scheduled. ++ * @param transport The underlying media transport to send and receive ++ * RTP/RTCP packets. ++ * @param p_tp Pointer to receive the media transport instance. ++ * ++ * @param close_slave ++ * Close the slave transport on transport_destroy. PJSUA-LIB ++ * sets this to PJ_FALSE because it takes care of this. ++ * ++ * @return PJ_SUCCESS on success, or the appropriate error code. ++ */ ++PJ_DECL(pj_status_t) pjmedia_transport_zrtp_create( pjmedia_endpt *endpt, ++ pj_timer_heap_t *timer_heap, ++ pjmedia_transport *transport, ++ pjmedia_transport **p_tp, ++ pj_bool_t close_slave); ++ ++/* ++ * Implement the specific ZRTP transport functions ++ */ ++ ++/** ++ * Initialize the ZRTP transport. ++ * ++ * Before an application can use ZRTP it has to initialize the ++ * ZRTP implementation. This method opens a file that contains ZRTP specific ++ * information such as the applications ZID (ZRTP id) and its ++ * retained shared secrets. ++ * ++ * Before an application initializes the ZRTP it may use ZRTP functions ++ * to set specific configuration data. See the relevant documentation ++ * in @c ZrtpCWrapper.h . The application can peform this after ++ * it created transport_zrtp. ++ * ++ * If one application requires several ZRTP sessions all ++ * sessions use the same timeout thread and use the same ZID ++ * file. Therefore an application does not need to do any ++ * synchronisation regading ZID files or timeouts. This is ++ * managed by the ZRTP implementation. ++ * ++ * The current implementation of ZRTP transport does not support ++ * different ZID files for one application instance. This ++ * restriction may be removed in later versions. ++ * ++ * The application may specify its own ZID file name. If no ++ * ZID file name is specified it defaults to ++ * $HOME/.GNUccRTP.zid if the HOME ++ * environment variable is set. If it is not set the current ++ * directory is used. ++ * ++ * If the method could set up the timeout thread and open the ZID ++ * file then it enables ZRTP processing and returns. ++ * ++ * @param tp ++ * Pointer to the ZRTP transport data as returned by ++ * @c pjmedia_transport_zrtp_create. ++ * ++ * @param zidFilename ++ * The name of the ZID file, can be a relative or absolut ++ * filename. ++ * ++ * @param autoEnable ++ * if set to true the method automatically sets enableZrtp to ++ * true. This enables the ZRTP auto-sense mode. ++ * ++ * @param zrtp_cb ++ * Pointer the application's ZRTP callbacks structure. Setting ++ * a NULL switches off the user callbacks ++ * @return ++ * PJ_SUCCESS on success, ZRTP processing enabled, other codes ++ * leave ZRTP processing disabled. ++ * ++ */ ++PJ_DECL(pj_status_t) pjmedia_transport_zrtp_initialize(pjmedia_transport *tp, ++ const char *zidFilename, ++ pj_bool_t autoEnable, ++ pjmedia_zrtp_cb *zrtp_cb); ++/** ++ * Enable or disable ZRTP processing. ++ * ++ * Call this method to enable or disable ZRTP processing after ++ * calling pjmedia_transport_zrtp_initialize with the ++ * parameter @c autoEnable set to false. This can be done before ++ * using a RTP session or at any time during a RTP session. ++ * ++ * Existing SRTP sessions or currently active ZRTP processing will ++ * not be stopped or disconnected. ++ * ++ * If the application enables ZRTP then: ++ *
    ++ *
  • ZRTP transport starts to send ZRTP Hello packets after at least ++ * one RTP packet was sent and received on the associated RTP ++ * session. Thus if an application enables ZRTP and ZRTP transport ++ * detects traffic on the RTP session then ZRTP transport automatically ++ * starts the ZRTP protocol. This automatic start is convenient ++ * for applications that negotiate RTP parameters and set up RTP ++ * sessions but the actual RTP traffic starts some time later. ++ *
  • ++ *
  • ZRTP transport analyses incoming packets to detect ZRTP ++ * messages. If ZRTP was started, either via automatic start (see ++ * above) or explicitly via @c zrtp_startZrtp, then ZrtpQueue ++ * forwards ZRTP packets to the GNU ZRTP core. ++ *
++ * ++ * @param tp ++ * Pointer to the ZRTP transport data as returned by ++ * @c pjmedia_transport_zrtp_create. ++ * ++ * @param onOff ++ * @c 1 to enable ZRTP, @c 0 to disable ZRTP ++ */ ++PJ_DECL(void) pjmedia_transport_zrtp_setEnableZrtp(pjmedia_transport *tp, pj_bool_t onOff); ++ ++/** ++ * Return the state of ZRTP enable state. ++ * ++ * @param tp ++ * Pointer to the ZRTP transport data as returned by ++ * @c pjmedia_transport_zrtp_create. ++ * ++ * @return @c true if ZRTP processing is enabled, @c false ++ * otherwise. ++ */ ++PJ_DECL(pj_bool_t) pjmedia_transport_zrtp_isEnableZrtp(pjmedia_transport *tp); ++ ++/** ++ * Starts the ZRTP protocol engine. ++ * ++ * Applications may call this method to immediatly start the ZRTP protocol ++ * engine any time after initializing ZRTP and setting optinal parameters, ++ * for example client id or multi-stream parameters. ++ * ++ * If the application does not call this method but sucessfully initialized ++ * the ZRTP engine using @c pjmedia_transport_zrtp_initialize then ZRTP may ++ * also start, depending on the autoEnable parameter. ++ * ++ * @param tp ++ * Pointer to the ZRTP transport data as returned by ++ * @c pjmedia_transport_zrtp_create. ++ * ++ * @see pjmedia_transport_zrtp_initialize ++ */ ++PJ_DECL(void) pjmedia_transport_zrtp_startZrtp(pjmedia_transport *tp); ++ ++/** ++ * Stops the ZRTP protocol engine. ++ * ++ * Applications call this method to stop the ZRTP protocol ++ * engine. The ZRTP transport can not start or process any ZRTP ++ * negotiations. ++ * ++ * This call does not deactivate SRTP processing of ZRTP transport, thus ++ * the ZRTP transport still encrypts/decrypts data via SRTP. ++ * ++ * @param tp ++ * Pointer to the ZRTP transport data as returned by ++ * @c pjmedia_transport_zrtp_create. ++ * ++ */ ++PJ_DECL(void) pjmedia_transport_zrtp_stopZrtp(pjmedia_transport *tp); ++ ++/** ++ * Set the local SSRC in case of receive-only sessions. ++ * ++ * Receiver-only RTP sessions never send RTP packets, thus ZRTP cannot learn ++ * the local (sender) SSRC. ZRTP requires the SSRC to bind the RTP session ++ * to the SRTP and its handshake. In this case the application shall generate ++ * a SSRC value and set it. ++ * ++ * Usually an application knows if a specific RTP session is receive-only, for ++ * example by inspecting and parsing the SDP data. ++ * ++ * If the application later decides to switch this RTP session to full-duplex ++ * mode (send and receive) it shall use the generated SSRC to intialize the ++ * RTP session. Then the outgoing packets are encrypted by SRTP. ++ * ++ * @param tp ++ * Pointer to the ZRTP transport data as returned by ++ * @c pjmedia_transport_zrtp_create. ++ * ++ * @param ssrc ++ * The local ssrc value in host order. ++ */ ++PJ_DECL(void) pjmedia_transport_zrtp_setLocalSSRC(pjmedia_transport *tp, uint32_t ssrc); ++ ++/** ++ * Check the state of the MitM mode flag. ++ * ++ * If true then this ZRTP session acts as MitM, usually enabled by a PBX ++ * client (user agent) ++ * ++ * @return state of mitmMode ++ */ ++PJ_DECL(pj_bool_t) pjmedia_transport_zrtp_isMitmMode(pjmedia_transport *tp); ++ ++/** ++ * Set the state of the MitM mode flag. ++ * ++ * If MitM mode is set to true this ZRTP session acts as MitM, usually ++ * enabled by a PBX client (user agent). ++ * ++ * @param mitmMode defines the new state of the mitmMode flag ++ */ ++PJ_DECL(void) pjmedia_transport_zrtp_setMitmMode(pjmedia_transport *tp, pj_bool_t mitmMode); ++ ++/** ++ * Set / reset the SAS verification flag. ++ * ++ */ ++PJ_DECL(void) pjmedia_transport_zrtp_setSASVerified(pjmedia_transport *tp, pj_bool_t verified); ++ ++/** ++ * Get the peer's ZID. ++ * ++ */ ++PJ_DECL(int) pjmedia_transport_zrtp_getPeerZid(pjmedia_transport *tp, unsigned char* data); ++ ++/** ++ * Get the peer's name. ++ * ++ */ ++PJ_DECL(char*) pjmedia_transport_zrtp_getPeerName(pjmedia_transport *tp); ++ ++/** ++ * Set the peer's name. ++ * ++ */ ++PJ_DECL(void) pjmedia_transport_zrtp_putPeerName(pjmedia_transport *tp, const char *name); ++ ++ ++PJ_DECL(char*) pjmedia_transport_zrtp_getMultiStreamParameters(pjmedia_transport *tp, pj_int32_t *length); ++ ++PJ_DECL(void) pjmedia_transport_zrtp_setMultiStreamParameters(pjmedia_transport *tp, const char *parameters, pj_int32_t length, pjmedia_transport *master_tp); ++ ++/** ++ * Get the ZRTP context pointer. ++ * ++ * Appplications need the ZRTP context pointer if they call ZRTP specific ++ * methods. The ZRTP specific include file @c ZrtpCWrapper contains the ++ * descriptions of the ZRTP methods. ++ * ++ * @return Pointer to ZRTP context ++ * ++ * @see zrtp_setAuxSecret() ++ * @see zrtp_setPbxSecret() ++ * @see zrtp_inState() ++ * @see zrtp_SASVerified() ++ * @see zrtp_resetSASVerified() ++ * @see zrtp_getHelloHash() ++ * @see zrtp_getMultiStrParams() ++ * @see zrtp_setMultiStrParams() ++ * @see zrtp_isMultiStream() ++ * @see zrtp_isMultiStreamAvailable() ++ * @see zrtp_acceptEnrollment() ++ * @see zrtp_setSignatureData() ++ * @see zrtp_getSignatureData() ++ * @see zrtp_getSignatureLength() ++ * @see zrtp_getZid(); ++ */ ++PJ_DECL(ZrtpContext*) pjmedia_transport_zrtp_getZrtpContext(pjmedia_transport *tp); ++ ++PJ_END_DECL ++ ++ ++/** ++ * @} ++ */ ++ ++#endif /* __PJMEDIA_TRANSPORT_ADAPTER_SAMPLE_H__ */ ++ ++ +diff -ruN pjproject-2.10/pjmedia/include/pjmedia/vid_stream.h pjsip/pjmedia/include/pjmedia/vid_stream.h +--- pjproject-2.10/pjmedia/include/pjmedia/vid_stream.h 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/pjmedia/include/pjmedia/vid_stream.h 2021-02-06 18:35:36.234362341 +0100 +@@ -471,6 +471,16 @@ + pjmedia_vid_stream_get_rtp_session_info(pjmedia_vid_stream *stream, + pjmedia_stream_rtp_sess_info *session_info); + ++/** ++ * Send RTCP PLI for the media stream. ++ * ++ * @param stream The media stream. ++ * ++ * @return PJ_SUCCESS on success. ++ */ ++PJ_DECL(pj_status_t) pjmedia_vid_stream_send_rtcp_pli( ++ pjmedia_vid_stream *stream); ++ + + /** + * @} +diff -ruN pjproject-2.10/pjmedia/include/pjmedia-audiodev/audiodev.h pjsip/pjmedia/include/pjmedia-audiodev/audiodev.h +--- pjproject-2.10/pjmedia/include/pjmedia-audiodev/audiodev.h 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/pjmedia/include/pjmedia-audiodev/audiodev.h 2021-02-06 16:53:28.395408793 +0100 +@@ -24,6 +24,7 @@ + * @file audiodev.h + * @brief Audio subsystem API. + */ ++#include + #include + #include + +@@ -99,10 +100,38 @@ + PJ_DECL(pj_status_t) + pjmedia_aud_unregister_factory(pjmedia_aud_dev_factory_create_func_ptr adf); + ++typedef enum pjmedia_aud_dev_event { ++ PJMEDIA_AUD_DEV_DEFAULT_INPUT_CHANGED, ++ PJMEDIA_AUD_DEV_DEFAULT_OUTPUT_CHANGED, ++ PJMEDIA_AUD_DEV_LIST_WILL_REFRESH, ++ PJMEDIA_AUD_DEV_LIST_DID_REFRESH ++} pjmedia_aud_dev_event; ++ ++ ++typedef void (*pjmedia_aud_dev_observer_callback)(pjmedia_aud_dev_event event); + + /** +- * @} ++ * This structure specifies the parameters to set an audio device observer ++ */ ++typedef struct pjmedia_aud_dev_observer { ++ pjmedia_aud_dev_observer_callback cb; ++ pj_pool_t *pool; ++ pj_mutex_t *lock; ++ pj_thread_t *thread; ++ pj_thread_desc thread_desc; ++} pjmedia_aud_dev_observer; ++ ++/** ++ * Set an audio device observer callback. ++ * ++ * @param cb The callback that needs to be registred, or NULL in ++ * in case it needs to be unregistered. Only one callback ++ * can be registered. ++ * ++ * @return PJ_SUCCESS on successful operation or the appropriate ++ * error code. + */ ++PJ_DECL(pj_status_t) pjmedia_aud_dev_set_observer_cb(pjmedia_aud_dev_observer_callback cb); + + PJ_END_DECL + +diff -ruN pjproject-2.10/pjmedia/include/pjmedia-audiodev/audiodev_imp.h pjsip/pjmedia/include/pjmedia-audiodev/audiodev_imp.h +--- pjproject-2.10/pjmedia/include/pjmedia-audiodev/audiodev_imp.h 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/pjmedia/include/pjmedia-audiodev/audiodev_imp.h 2021-02-06 18:37:31.685678395 +0100 +@@ -29,6 +29,15 @@ + * @{ + */ + ++typedef enum pjmedia_aud_dev_change_event { ++ DEFAULT_INPUT_CHANGED = 1, ++ DEFAULT_OUTPUT_CHANGED, ++ DEVICE_LIST_CHANGED ++} pjmedia_aud_dev_change_event; ++ ++typedef void (*pjmedia_aud_dev_change_callback)(pjmedia_aud_dev_change_event event); ++ ++ + /** + * Sound device factory operations. + */ +@@ -99,6 +108,30 @@ + */ + pj_status_t (*refresh)(pjmedia_aud_dev_factory *f); + ++ /** ++ * Set audio device change callback ++ * ++ * @param f The audio device factory. ++ * @param cb The audio device change callback. ++ */ ++ void (*set_dev_change_cb)(pjmedia_aud_dev_factory *f, ++ pjmedia_aud_dev_change_callback cb); ++ ++ /** ++ * Get default recording device index ++ * ++ * @param f The audio device factory. ++ */ ++ int (*get_default_rec_dev)(pjmedia_aud_dev_factory *f); ++ ++ /** ++ * Get default playback device index ++ * ++ * @param f The audio device factory. ++ */ ++ int (*get_default_play_dev)(pjmedia_aud_dev_factory *f); ++ ++ + } pjmedia_aud_dev_factory_op; + + +diff -ruN pjproject-2.10/pjmedia/include/pjmedia.h pjsip/pjmedia/include/pjmedia.h +--- pjproject-2.10/pjmedia/include/pjmedia.h 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/pjmedia/include/pjmedia.h 2021-02-06 18:40:23.766607985 +0100 +@@ -44,6 +44,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -67,6 +68,7 @@ + #include + #include + #include ++#include + #include + #include + #include +diff -ruN pjproject-2.10/pjmedia/include/pjmedia-videodev/fb_dev.h pjsip/pjmedia/include/pjmedia-videodev/fb_dev.h +--- pjproject-2.10/pjmedia/include/pjmedia-videodev/fb_dev.h 1970-01-01 01:00:00.000000000 +0100 ++++ pjsip/pjmedia/include/pjmedia-videodev/fb_dev.h 2021-02-06 18:43:39.220193284 +0100 +@@ -0,0 +1,32 @@ ++/* $Id$ */ ++/* ++ * Copyright (C) 2014-present AG Projects ++ * Copyright (C) 2013-2014 Teluu Inc. (http://www.teluu.com) ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ++ */ ++#ifndef PJMEDIA_VIDEODEV_FB_DEV_H__ ++#define PJMEDIA_VIDEODEV_FB_DEV_H__ ++ ++#include ++ ++typedef void (*pjmedia_vid_dev_fb_frame_cb)(const pjmedia_frame *frame, const pjmedia_rect_size size, void *user_data); ++ ++pj_status_t ++pjmedia_vid_dev_fb_set_callback(pjmedia_vid_dev_stream *strm, ++ pjmedia_vid_dev_fb_frame_cb cb, ++ void *user_data); ++ ++#endif /* PJMEDIA_VIDEODEV_FB_DEV_H__ */ +diff -ruN pjproject-2.10/pjmedia/include/pjmedia_videodev.h pjsip/pjmedia/include/pjmedia_videodev.h +--- pjproject-2.10/pjmedia/include/pjmedia_videodev.h 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/pjmedia/include/pjmedia_videodev.h 2021-02-06 18:39:51.429682633 +0100 +@@ -27,5 +27,6 @@ + #include + #include + #include ++#include + + #endif /* __PJMEDIA_VIDEODEV_H__ */ +diff -ruN pjproject-2.10/pjmedia/src/pjmedia/converter.c pjsip/pjmedia/src/pjmedia/converter.c +--- pjproject-2.10/pjmedia/src/pjmedia/converter.c 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/pjmedia/src/pjmedia/converter.c 2021-02-06 20:19:28.491163002 +0100 +@@ -19,6 +19,7 @@ + #include + #include + #include ++#include + + #define THIS_FILE "converter.c" + +@@ -174,6 +175,24 @@ + if (status != PJ_SUCCESS) + return status; + ++ if (param->src.type == PJMEDIA_TYPE_VIDEO) { ++ char src_fourcc_name[5]; ++ char dst_fourcc_name[5]; ++ PJ_LOG(4, (THIS_FILE, "Converter %p (%s) created for video: %dx%d %s -> %dx%d %s", ++ cv, ++ f->name, ++ param->src.det.vid.size.w, ++ param->src.det.vid.size.h, ++ pjmedia_fourcc_name(param->src.id, src_fourcc_name), ++ param->dst.det.vid.size.w, ++ param->dst.det.vid.size.h, ++ pjmedia_fourcc_name(param->dst.id, dst_fourcc_name))); ++ } else if (param->src.type == PJMEDIA_TYPE_AUDIO) { ++ PJ_LOG(4, (THIS_FILE, "Converter %p created for audio", cv)); ++ } else { ++ PJ_LOG(4, (THIS_FILE, "Converter %p created for unknown", cv)); ++ } ++ + *p_cv = cv; + + return PJ_SUCCESS; +@@ -188,6 +207,7 @@ + + PJ_DEF(void) pjmedia_converter_destroy(pjmedia_converter *cv) + { ++ PJ_LOG(4, (THIS_FILE, "Converter %p destroyed", cv)); + (*cv->op->destroy)(cv); + } + +diff -ruN pjproject-2.10/pjmedia/src/pjmedia/echo_common.c pjsip/pjmedia/src/pjmedia/echo_common.c +--- pjproject-2.10/pjmedia/src/pjmedia/echo_common.c 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/pjmedia/src/pjmedia/echo_common.c 2021-02-06 20:22:22.888063103 +0100 +@@ -135,7 +135,7 @@ + #if defined(PJMEDIA_HAS_WEBRTC_AEC) && PJMEDIA_HAS_WEBRTC_AEC!=0 + static struct ec_operations webrtc_aec_op = + { +- "WebRTC AEC", ++ "WEBRTC AEC", + &webrtc_aec_create, + &webrtc_aec_destroy, + &webrtc_aec_reset, +diff -ruN pjproject-2.10/pjmedia/src/pjmedia/echo_webrtc_aec.c pjsip/pjmedia/src/pjmedia/echo_webrtc_aec.c +--- pjproject-2.10/pjmedia/src/pjmedia/echo_webrtc_aec.c 1970-01-01 01:00:00.000000000 +0100 ++++ pjsip/pjmedia/src/pjmedia/echo_webrtc_aec.c 2021-02-06 18:45:18.907037776 +0100 +@@ -0,0 +1,643 @@ ++/** ++ * Copyright (C) 2011-2013 AG Projects ++ * Copyright (C) 2010 Regis Montoya (aka r3gis - www.r3gis.fr) ++ * ++ * Licensed under the Apache License, Version 2.0 (the "License"); ++ * you may not use this file except in compliance with the License. ++ * You may obtain a copy of the License at ++ * ++ * http://www.apache.org/licenses/LICENSE-2.0 ++ * ++ * Unless required by applicable law or agreed to in writing, software ++ * distributed under the License is distributed on an "AS IS" BASIS, ++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ++ * See the License for the specific language governing permissions and ++ * limitations under the License. ++ */ ++ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++ ++#if defined(PJMEDIA_HAS_WEBRTC_AEC) && PJMEDIA_HAS_WEBRTC_AEC != 0 ++ ++/* 0: conservative, 1: moderate, 2: aggresive */ ++#ifndef PJMEDIA_WEBRTC_AEC_AGGRESSIVENESS ++ #define PJMEDIA_WEBRTC_AEC_AGGRESSIVENESS 2 ++#endif ++ ++/* 0: mild, 1: mediumn, 2: aggressive */ ++#ifndef PJMEDIA_WEBRTC_NS_POLICY ++ #define PJMEDIA_WEBRTC_NS_POLICY 0 ++#endif ++ ++#define THIS_FILE "echo_webrtc_aec.c" ++ ++#include ++#include ++#include ++#include ++ ++#include "echo_internal.h" ++ ++ ++/* ++ * This file contains the implementation of an echo canceller and noise suppressor for PJSIP which uses components ++ * from the WebRTC project. Things to take into account: ++ * ++ * - The WebRTC engine works with 10ms frames, while in PJSIP we use 20ms frames mostly, all data fed to WebRTC elements needs ++ * to be chunked in 10ms chunks. ++ * - When a 32kHz sampling rate is used, the WebRTC engine needs frames to be passed split into low and high frequencies. PJSIP ++ * will give us a frame with all frequencies, so the signal processing library in WebRTC must be used to split frames into low ++ * and high frequencies, and combine them later. ++ */ ++ ++ ++typedef struct AudioBuffer ++{ ++ int samples_per_channel; ++ pj_bool_t is_split; ++ ++ WebRtc_Word16* data; ++ WebRtc_Word16 low_pass_data[160]; ++ WebRtc_Word16 high_pass_data[160]; ++ ++ WebRtc_Word32 analysis_filter_state1[6]; ++ WebRtc_Word32 analysis_filter_state2[6]; ++ WebRtc_Word32 synthesis_filter_state1[6]; ++ WebRtc_Word32 synthesis_filter_state2[6]; ++} AudioBuffer; ++ ++static WebRtc_Word16* AudioBuffer_GetData(AudioBuffer *ab); ++static WebRtc_Word16* AudioBuffer_GetLowPassData(AudioBuffer *ab); ++static WebRtc_Word16* AudioBuffer_GetHighPassData(AudioBuffer *ab); ++static void AudioBuffer_SetData(AudioBuffer *ab, WebRtc_Word16 *data); ++static void AudioBuffer_Initialize(AudioBuffer *ab, int sample_rate); ++static int AudioBuffer_SamplesPerChannel(AudioBuffer *ab); ++ ++ ++static WebRtc_Word16* AudioBuffer_GetData(AudioBuffer *ab) ++{ ++ pj_assert(ab->data); ++ ++ if (ab->is_split) { ++ WebRtcSpl_SynthesisQMF(ab->low_pass_data, ++ ab->high_pass_data, ++ ab->data, ++ ab->synthesis_filter_state1, ++ ab->synthesis_filter_state2); ++ } ++ return ab->data; ++} ++ ++ ++static WebRtc_Word16* AudioBuffer_GetLowPassData(AudioBuffer *ab) ++{ ++ if (!ab->is_split) { ++ return ab->data; ++ } else { ++ return ab->low_pass_data; ++ } ++} ++ ++ ++static WebRtc_Word16* AudioBuffer_GetHighPassData(AudioBuffer *ab) ++{ ++ if (!ab->is_split) { ++ return ab->data; ++ } else { ++ return ab->high_pass_data; ++ } ++} ++ ++ ++static void AudioBuffer_Initialize(AudioBuffer *ab, int sample_rate) ++{ ++ pj_bzero(ab, sizeof(AudioBuffer)); ++ if (sample_rate == 32000) { ++ ab->is_split = PJ_TRUE; ++ ab->samples_per_channel = 160; ++ } else { ++ ab->is_split = PJ_FALSE; ++ ab->samples_per_channel = sample_rate / 100; ++ } ++} ++ ++ ++static void AudioBuffer_SetData(AudioBuffer *ab, WebRtc_Word16 *data) ++{ ++ ab->data = data; ++ if (ab->is_split) { ++ /* split data into low and high bands */ ++ WebRtcSpl_AnalysisQMF(ab->data, /* input data */ ++ ab->low_pass_data, /* pointer to low pass data storage*/ ++ ab->high_pass_data, /* pointer to high pass data storage*/ ++ ab->analysis_filter_state1, ++ ab->analysis_filter_state2); ++ } ++} ++ ++ ++static int AudioBuffer_SamplesPerChannel(AudioBuffer *ab) ++{ ++ return ab->samples_per_channel; ++} ++ ++ ++const WebRtc_Word16 kFilterCoefficients8kHz[5] = ++ {3798, -7596, 3798, 7807, -3733}; ++ ++const WebRtc_Word16 kFilterCoefficients[5] = ++ {4012, -8024, 4012, 8002, -3913}; ++ ++typedef struct { ++ WebRtc_Word16 y[4]; ++ WebRtc_Word16 x[2]; ++ const WebRtc_Word16* ba; ++} HighPassFilterState; ++ ++ ++static int HighPassFilter_Initialize(HighPassFilterState* hpf, int sample_rate) { ++ assert(hpf != NULL); ++ ++ if (sample_rate == 8000) { ++ hpf->ba = kFilterCoefficients8kHz; ++ } else { ++ hpf->ba = kFilterCoefficients; ++ } ++ ++ WebRtcSpl_MemSetW16(hpf->x, 0, 2); ++ WebRtcSpl_MemSetW16(hpf->y, 0, 4); ++ ++ return 0; ++} ++ ++ ++static int HighPassFilter_Process(HighPassFilterState* hpf, WebRtc_Word16* data, int length) { ++ assert(hpf != NULL); ++ ++ int i; ++ WebRtc_Word32 tmp_int32 = 0; ++ WebRtc_Word16* y = hpf->y; ++ WebRtc_Word16* x = hpf->x; ++ const WebRtc_Word16* ba = hpf->ba; ++ ++ for (i = 0; i < length; i++) { ++ // y[i] = b[0] * x[i] + b[1] * x[i-1] + b[2] * x[i-2] ++ // + -a[1] * y[i-1] + -a[2] * y[i-2]; ++ ++ tmp_int32 = WEBRTC_SPL_MUL_16_16(y[1], ba[3]); // -a[1] * y[i-1] (low part) ++ tmp_int32 += WEBRTC_SPL_MUL_16_16(y[3], ba[4]); // -a[2] * y[i-2] (low part) ++ tmp_int32 = (tmp_int32 >> 15); ++ tmp_int32 += WEBRTC_SPL_MUL_16_16(y[0], ba[3]); // -a[1] * y[i-1] (high part) ++ tmp_int32 += WEBRTC_SPL_MUL_16_16(y[2], ba[4]); // -a[2] * y[i-2] (high part) ++ tmp_int32 = (tmp_int32 << 1); ++ ++ tmp_int32 += WEBRTC_SPL_MUL_16_16(data[i], ba[0]); // b[0]*x[0] ++ tmp_int32 += WEBRTC_SPL_MUL_16_16(x[0], ba[1]); // b[1]*x[i-1] ++ tmp_int32 += WEBRTC_SPL_MUL_16_16(x[1], ba[2]); // b[2]*x[i-2] ++ ++ // Update state (input part) ++ x[1] = x[0]; ++ x[0] = data[i]; ++ ++ // Update state (filtered part) ++ y[2] = y[0]; ++ y[3] = y[1]; ++ y[0] = (WebRtc_Word16)(tmp_int32 >> 13); ++ y[1] = (WebRtc_Word16)((tmp_int32 - WEBRTC_SPL_LSHIFT_W32((WebRtc_Word32)(y[0]), 13)) << 2); ++ ++ // Rounding in Q12, i.e. add 2^11 ++ tmp_int32 += 2048; ++ ++ // Saturate (to 2^27) so that the HP filtered signal does not overflow ++ tmp_int32 = WEBRTC_SPL_SAT((WebRtc_Word32)(134217727), tmp_int32, (WebRtc_Word32)(-134217728)); ++ ++ // Convert back to Q0 and use rounding ++ data[i] = (WebRtc_Word16)WEBRTC_SPL_RSHIFT_W32(tmp_int32, 12); ++ ++ } ++ ++ return 0; ++} ++ ++ ++typedef struct webrtc_ec ++{ ++ void *AEC_inst; ++ void *AGC_inst; ++ NsHandle *NS_inst; ++ ++ pj_bool_t needs_reset; ++ unsigned skip_frames; ++ unsigned silence_frames; ++ ++ unsigned clock_rate; ++ unsigned echo_tail; ++ unsigned samples_per_frame; ++ unsigned samples_per_10ms_frame; ++ ++ WebRtc_Word32 mic_capture_level; ++ WebRtc_Word16 has_echo; ++ WebRtc_UWord8 is_saturated; ++ ++ HighPassFilterState hpf; ++ AudioBuffer capture_audio_buffer; ++ AudioBuffer playback_audio_buffer; ++ ++ pj_int16_t *tmp_frame; ++ pj_int16_t *empty_frame; ++} webrtc_ec; ++ ++ ++#define WEBRTC_AEC_ERROR(aec_inst, tag) \ ++ do { \ ++ unsigned status = WebRtcAec_get_error_code(aec_inst); \ ++ PJ_LOG(4, (THIS_FILE, "WebRTC AEC ERROR (%s) %d", tag, status)); \ ++ } while (0) \ ++ ++ ++#define WEBRTC_AGC_ERROR(ns_inst, text) \ ++ do { \ ++ PJ_LOG(4, (THIS_FILE, "WebRTC AGC ERROR (%s)", text)); \ ++ } while (0) \ ++ ++ ++#define WEBRTC_NS_ERROR(ns_inst, text) \ ++ do { \ ++ PJ_LOG(4, (THIS_FILE, "WebRTC NS ERROR (%s)", text)); \ ++ } while (0) \ ++ ++ ++PJ_DEF(pj_status_t) webrtc_aec_create(pj_pool_t *pool, ++ unsigned clock_rate, ++ unsigned channel_count, ++ unsigned samples_per_frame, ++ unsigned tail_ms, ++ unsigned options, ++ void **p_echo ) ++{ ++ webrtc_ec *echo; ++ int status; ++ ++ *p_echo = NULL; ++ ++ if (clock_rate != 16000 && clock_rate != 32000) { ++ PJ_LOG(4, (THIS_FILE, "Unsupported sample rate: %d", clock_rate)); ++ return PJ_EINVAL; ++ } ++ ++ echo = PJ_POOL_ZALLOC_T(pool, webrtc_ec); ++ PJ_ASSERT_RETURN(echo != NULL, PJ_ENOMEM); ++ ++ status = WebRtcAec_Create(&echo->AEC_inst); ++ if(status != 0) { ++ PJ_LOG(4, (THIS_FILE, "Couldn't allocate memory for WebRTC AEC")); ++ goto error; ++ } ++ ++ status = WebRtcAec_Init(echo->AEC_inst, clock_rate, clock_rate); ++ if(status != 0) { ++ WEBRTC_AEC_ERROR(echo->AEC_inst, "initialization"); ++ goto error; ++ } ++ ++ AecConfig aec_config; ++ aec_config.nlpMode = PJMEDIA_WEBRTC_AEC_AGGRESSIVENESS; ++ aec_config.skewMode = kAecFalse; ++ aec_config.metricsMode = kAecFalse; ++ ++ status = WebRtcAec_set_config(echo->AEC_inst, aec_config); ++ if(status != 0) { ++ WEBRTC_AEC_ERROR(echo->AEC_inst, "config initialization"); ++ goto error; ++ } ++ ++ status = WebRtcAgc_Create(&echo->AGC_inst); ++ if(status != 0) { ++ PJ_LOG(4, (THIS_FILE, "Couldn't allocate memory for WebRTC AGC")); ++ goto error; ++ } ++ ++ status = WebRtcAgc_Init(echo->AGC_inst, 0, 255, kAgcModeAdaptiveAnalog, clock_rate); ++ if(status != 0) { ++ WEBRTC_AGC_ERROR(echo->AGC_inst, "initialization"); ++ goto error; ++ } ++ ++ WebRtcAgc_config_t agc_config; ++ agc_config.targetLevelDbfs = 7; ++ agc_config.compressionGaindB = 0; ++ agc_config.limiterEnable = kAgcFalse; ++ ++ status = WebRtcAgc_set_config(echo->AGC_inst, agc_config); ++ if(status != 0) { ++ WEBRTC_AGC_ERROR(echo->AGC_inst, "config initialization"); ++ goto error; ++ } ++ ++ status = WebRtcNs_Create(&echo->NS_inst); ++ if(status != 0) { ++ PJ_LOG(4, (THIS_FILE, "Couldn't allocate memory for WebRTC NS")); ++ goto error; ++ } ++ ++ status = WebRtcNs_Init(echo->NS_inst, clock_rate); ++ if(status != 0) { ++ WEBRTC_NS_ERROR(echo->NS_inst, "initialization"); ++ goto error; ++ } ++ ++ status = WebRtcNs_set_policy(echo->NS_inst, PJMEDIA_WEBRTC_NS_POLICY); ++ if (status != 0) { ++ WEBRTC_NS_ERROR(echo->NS_inst, "failed to set policy"); ++ } ++ ++ echo->clock_rate = clock_rate; ++ echo->samples_per_frame = samples_per_frame; ++ echo->samples_per_10ms_frame = clock_rate / 100; /* the WebRTC engine works with 10ms frames */ ++ echo->echo_tail = tail_ms; ++ echo->needs_reset = PJ_TRUE; ++ echo->skip_frames = 0; ++ echo->silence_frames = 0; ++ echo->mic_capture_level = 255; /* initial mic capture level, maximum */ ++ ++ /* Allocate temporary frames for echo cancellation */ ++ echo->tmp_frame = (pj_int16_t*) pj_pool_zalloc(pool, sizeof(pj_int16_t)*samples_per_frame); ++ PJ_ASSERT_RETURN(echo->tmp_frame, PJ_ENOMEM); ++ ++ echo->empty_frame = (pj_int16_t*) pj_pool_zalloc(pool, sizeof(pj_int16_t)*samples_per_frame); ++ PJ_ASSERT_RETURN(echo->empty_frame, PJ_ENOMEM); ++ ++ /* Initialize audio buffers */ ++ AudioBuffer_Initialize(&echo->capture_audio_buffer, clock_rate); ++ AudioBuffer_Initialize(&echo->playback_audio_buffer, clock_rate); ++ ++ /* Initialize high pass filter */ ++ HighPassFilter_Initialize(&echo->hpf, clock_rate); ++ ++ PJ_LOG(4, (THIS_FILE, "WebRTC AEC and NS initialized")); ++ *p_echo = echo; ++ return PJ_SUCCESS; ++ ++error: ++ if (echo->AEC_inst) ++ WebRtcAec_Free(echo->AEC_inst); ++ if (echo->AGC_inst) ++ WebRtcAgc_Free(echo->AGC_inst); ++ if (echo->NS_inst) ++ WebRtcNs_Free(echo->NS_inst); ++ return PJ_EBUG; ++} ++ ++ ++PJ_DEF(pj_status_t) webrtc_aec_destroy(void *state ) ++{ ++ webrtc_ec *echo = (webrtc_ec*) state; ++ PJ_ASSERT_RETURN(echo, PJ_EINVAL); ++ ++ if (echo->AEC_inst) { ++ WebRtcAec_Free(echo->AEC_inst); ++ echo->AEC_inst = NULL; ++ } ++ if (echo->AGC_inst) { ++ WebRtcAgc_Free(echo->AGC_inst); ++ echo->AGC_inst = NULL; ++ } ++ if (echo->NS_inst) { ++ WebRtcNs_Free(echo->NS_inst); ++ echo->NS_inst = NULL; ++ } ++ ++ return PJ_SUCCESS; ++} ++ ++ ++PJ_DEF(void) webrtc_aec_reset(void *state) ++{ ++ /* Synchronously reset later, before processing the next frame, to avoid race conditions */ ++ ((webrtc_ec*)state)->needs_reset = PJ_TRUE; ++} ++ ++ ++static void aec_reset(webrtc_ec *echo) ++{ ++ PJ_ASSERT_ON_FAIL(echo && echo->AEC_inst && echo->AGC_inst && echo->NS_inst, {return;}); ++ ++ int status = 0; ++ ++ /* re-initialize the AEC */ ++ status = WebRtcAec_Init(echo->AEC_inst, echo->clock_rate, echo->clock_rate); ++ if(status != 0) { ++ WEBRTC_AEC_ERROR(echo->AEC_inst, "re-initialization"); ++ return; ++ } ++ ++ AecConfig aec_config; ++ aec_config.nlpMode = PJMEDIA_WEBRTC_AEC_AGGRESSIVENESS; ++ aec_config.skewMode = kAecFalse; ++ aec_config.metricsMode = kAecFalse; ++ ++ status = WebRtcAec_set_config(echo->AEC_inst, aec_config); ++ if(status != 0) { ++ WEBRTC_AEC_ERROR(echo->AEC_inst, "configuration re-initialization"); ++ return; ++ } ++ ++ /* re-initialize the AGC */ ++ status = WebRtcAgc_Init(echo->AGC_inst, 0, 255, kAgcModeAdaptiveAnalog, echo->clock_rate); ++ if(status != 0) { ++ WEBRTC_AGC_ERROR(echo->AGC_inst, "initialization"); ++ return; ++ } ++ ++ WebRtcAgc_config_t agc_config; ++ agc_config.targetLevelDbfs = 7; ++ agc_config.compressionGaindB = 0; ++ agc_config.limiterEnable = kAgcFalse; ++ ++ status = WebRtcAgc_set_config(echo->AGC_inst, agc_config); ++ if(status != 0) { ++ WEBRTC_AGC_ERROR(echo->AGC_inst, "config initialization"); ++ return; ++ } ++ ++ /* re-initialize the NS */ ++ status = WebRtcNs_Init(echo->NS_inst, echo->clock_rate); ++ if(status != 0) { ++ WEBRTC_NS_ERROR(echo->NS_inst, "re-initialization"); ++ return; ++ } ++ ++ status = WebRtcNs_set_policy(echo->NS_inst, PJMEDIA_WEBRTC_NS_POLICY); ++ if (status != 0) { ++ WEBRTC_NS_ERROR(echo->NS_inst, "configuration re-initialization"); ++ return; ++ } ++ ++ /* re-initialize audio buffers */ ++ AudioBuffer_Initialize(&echo->capture_audio_buffer, echo->clock_rate); ++ AudioBuffer_Initialize(&echo->playback_audio_buffer, echo->clock_rate); ++ ++ /* re-initialize high pass filter state */ ++ HighPassFilter_Initialize(&echo->hpf, echo->clock_rate); ++ ++ /* re-initialize mic level */ ++ echo->mic_capture_level = 255; ++ ++ PJ_LOG(4, (THIS_FILE, "WebRTC AEC reset succeeded")); ++} ++ ++ ++/* ++ * Perform echo cancellation. ++ */ ++PJ_DEF(pj_status_t) webrtc_aec_cancel_echo(void *state, ++ pj_int16_t *rec_frm, ++ const pj_int16_t *play_frm, ++ unsigned options, ++ void *reserved) ++{ ++ webrtc_ec *echo = (webrtc_ec*) state; ++ pj_int16_t *capture_frame, *result_frame; ++ int i, status; ++ ++ /* Sanity checks */ ++ PJ_ASSERT_RETURN(echo && echo->AEC_inst && echo->AGC_inst && echo->NS_inst, PJ_EINVAL); ++ PJ_ASSERT_RETURN(rec_frm && play_frm && options==0 && reserved==NULL, PJ_EINVAL); ++ ++ /* Check if a reset is needed */ ++ if (echo->needs_reset) { ++ aec_reset(echo); ++ echo->needs_reset = PJ_FALSE; ++ echo->skip_frames = 15; ++ echo->silence_frames = 10; ++ } ++ ++ if (echo->skip_frames) { ++ echo->skip_frames--; ++ capture_frame = echo->empty_frame; ++ result_frame = echo->empty_frame; ++ } else if (echo->silence_frames) { ++ echo->silence_frames--; ++ capture_frame = rec_frm; ++ result_frame = echo->empty_frame; ++ } else { ++ capture_frame = rec_frm; ++ result_frame = echo->tmp_frame; ++ } ++ ++ /* Copy record frame to a temporary buffer, in case things go wrong audio will be returned unchanged */ ++ pjmedia_copy_samples(echo->tmp_frame, capture_frame, echo->samples_per_frame); ++ ++ for(i=0; i < echo->samples_per_frame; i+= echo->samples_per_10ms_frame) { ++ /* feed a 10ms frame into the audio buffers */ ++ AudioBuffer_SetData(&echo->capture_audio_buffer, (WebRtc_Word16 *) (&echo->tmp_frame[i])); ++ AudioBuffer_SetData(&echo->playback_audio_buffer, (WebRtc_Word16 *) (&play_frm[i])); ++ ++ /* Apply high pass filer */ ++ HighPassFilter_Process(&echo->hpf, ++ AudioBuffer_GetLowPassData(&echo->capture_audio_buffer), ++ AudioBuffer_SamplesPerChannel(&echo->capture_audio_buffer)); ++ ++ /* Analyze capture data gain ++ * NOTE: if we used kAgcModeAdaptiveDigital we'd use WebRtcAgc_VirtualMic instead ++ */ ++ status = WebRtcAgc_AddMic(echo->AGC_inst, ++ AudioBuffer_GetLowPassData(&echo->capture_audio_buffer), ++ AudioBuffer_GetHighPassData(&echo->capture_audio_buffer), ++ AudioBuffer_SamplesPerChannel(&echo->capture_audio_buffer)); ++ if(status != 0) { ++ WEBRTC_AGC_ERROR(echo->AGC_inst, "gain analysis"); ++ return PJ_EBUG; ++ } ++ ++ /* Feed farend buffer to AGC */ ++ status = WebRtcAgc_AddFarend(echo->AGC_inst, ++ AudioBuffer_GetLowPassData(&echo->playback_audio_buffer), ++ AudioBuffer_SamplesPerChannel(&echo->playback_audio_buffer)); ++ if(status != 0) { ++ WEBRTC_AGC_ERROR(echo->AGC_inst, "farend buffering"); ++ return PJ_EBUG; ++ } ++ ++ /* Feed farend buffer to AEC */ ++ status = WebRtcAec_BufferFarend(echo->AEC_inst, ++ AudioBuffer_GetLowPassData(&echo->playback_audio_buffer), ++ AudioBuffer_SamplesPerChannel(&echo->playback_audio_buffer)); ++ if(status != 0) { ++ WEBRTC_AEC_ERROR(echo->AEC_inst, "farend buffering"); ++ return PJ_EBUG; ++ } ++ ++ /* Noise suppression */ ++ status = WebRtcNs_Process(echo->NS_inst, ++ AudioBuffer_GetLowPassData(&echo->capture_audio_buffer), ++ AudioBuffer_GetHighPassData(&echo->capture_audio_buffer), ++ AudioBuffer_GetLowPassData(&echo->capture_audio_buffer), ++ AudioBuffer_GetHighPassData(&echo->capture_audio_buffer)); ++ if (status != 0) { ++ WEBRTC_NS_ERROR(echo->NS_inst, "ns processing"); ++ return PJ_EBUG; ++ } ++ ++ /* Process echo cancellation */ ++ status = WebRtcAec_Process(echo->AEC_inst, ++ AudioBuffer_GetLowPassData(&echo->capture_audio_buffer), ++ AudioBuffer_GetHighPassData(&echo->capture_audio_buffer), ++ AudioBuffer_GetLowPassData(&echo->capture_audio_buffer), ++ AudioBuffer_GetHighPassData(&echo->capture_audio_buffer), ++ AudioBuffer_SamplesPerChannel(&echo->capture_audio_buffer), ++ echo->echo_tail, ++ 0); ++ if(status != 0) { ++ WEBRTC_AEC_ERROR(echo->AEC_inst, "echo processing"); ++ return PJ_EBUG; ++ } ++ ++ WebRtcAec_get_echo_status(echo->AEC_inst, &echo->has_echo); ++#if 0 ++ if (echo->has_echo) { ++ PJ_LOG(4, (THIS_FILE, "Sound might have echo")); ++ } ++#endif ++ ++ /* Process gain control */ ++ status = WebRtcAgc_Process(echo->AGC_inst, ++ AudioBuffer_GetLowPassData(&echo->capture_audio_buffer), ++ AudioBuffer_GetHighPassData(&echo->capture_audio_buffer), ++ AudioBuffer_SamplesPerChannel(&echo->capture_audio_buffer), ++ AudioBuffer_GetLowPassData(&echo->capture_audio_buffer), ++ AudioBuffer_GetHighPassData(&echo->capture_audio_buffer), ++ echo->mic_capture_level, ++ &echo->mic_capture_level, ++ echo->has_echo, ++ &echo->is_saturated); ++ if (status != 0) { ++ WEBRTC_AGC_ERROR(echo->AGC_inst, "agc processing"); ++ return PJ_EBUG; ++ } ++#if 0 ++ if (echo->is_saturated) { ++ PJ_LOG(4, (THIS_FILE, "Sound might be saturated")); ++ } ++#endif ++ ++ /* finish frame processing, in case we are working at 32kHz low and high bands will be combined */ ++ AudioBuffer_GetData(&echo->capture_audio_buffer); ++ } ++ ++ /* Copy temporary buffer back to original rec_frm */ ++ pjmedia_copy_samples(rec_frm, result_frame, echo->samples_per_frame); ++ ++ return PJ_SUCCESS; ++ ++} ++ ++ ++#endif +diff -ruN pjproject-2.10/pjmedia/src/pjmedia/endpoint.c pjsip/pjmedia/src/pjmedia/endpoint.c +--- pjproject-2.10/pjmedia/src/pjmedia/endpoint.c 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/pjmedia/src/pjmedia/endpoint.c 2021-02-06 20:23:26.353848918 +0100 +@@ -688,6 +688,7 @@ + /* Put bandwidth info in media level using bandwidth modifier "TIAS" + * (RFC3890). + */ ++#if 0 + if (max_bitrate && pjmedia_add_bandwidth_tias_in_sdp) { + const pj_str_t STR_BANDW_MODIFIER = { "TIAS", 4 }; + pjmedia_sdp_bandw *b; +@@ -697,6 +698,7 @@ + b->value = max_bitrate; + m->bandw[m->bandw_count++] = b; + } ++#endif + + *p_m = m; + return PJ_SUCCESS; +diff -ruN pjproject-2.10/pjmedia/src/pjmedia/ffmpeg_util.c pjsip/pjmedia/src/pjmedia/ffmpeg_util.c +--- pjproject-2.10/pjmedia/src/pjmedia/ffmpeg_util.c 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/pjmedia/src/pjmedia/ffmpeg_util.c 2021-02-06 20:25:00.472499296 +0100 +@@ -34,10 +34,11 @@ + enum AVPixelFormat pf; + } ffmpeg_fmt_table[] = + { ++ { PJMEDIA_FORMAT_ARGB, AV(PIX_FMT_ARGB)}, + { PJMEDIA_FORMAT_RGBA, AV(PIX_FMT_RGBA)}, + { PJMEDIA_FORMAT_RGB24,AV(PIX_FMT_BGR24)}, + { PJMEDIA_FORMAT_BGRA, AV(PIX_FMT_BGRA)}, +- { PJMEDIA_FORMAT_GBRP, AV(PIX_FMT_GBRP)}, ++ { PJMEDIA_FORMAT_GBRP, AV(PIX_FMT_GBR24P)}, + + { PJMEDIA_FORMAT_AYUV, AV(PIX_FMT_NONE)}, + { PJMEDIA_FORMAT_YUY2, AV(PIX_FMT_YUYV422)}, +diff -ruN pjproject-2.10/pjmedia/src/pjmedia/format.c pjsip/pjmedia/src/pjmedia/format.c +--- pjproject-2.10/pjmedia/src/pjmedia/format.c 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/pjmedia/src/pjmedia/format.c 2021-02-06 20:25:25.677209428 +0100 +@@ -77,6 +77,7 @@ + static pjmedia_video_format_info built_in_vid_fmt_info[] = + { + {PJMEDIA_FORMAT_RGB24, "RGB24", PJMEDIA_COLOR_MODEL_RGB, 24, 1, &apply_packed_fmt}, ++ {PJMEDIA_FORMAT_ARGB, "ARGB", PJMEDIA_COLOR_MODEL_RGB, 32, 1, &apply_packed_fmt}, + {PJMEDIA_FORMAT_RGBA, "RGBA", PJMEDIA_COLOR_MODEL_RGB, 32, 1, &apply_packed_fmt}, + {PJMEDIA_FORMAT_BGRA, "BGRA", PJMEDIA_COLOR_MODEL_RGB, 32, 1, &apply_packed_fmt}, + {PJMEDIA_FORMAT_DIB , "DIB ", PJMEDIA_COLOR_MODEL_RGB, 24, 1, &apply_packed_fmt}, +diff -ruN pjproject-2.10/pjmedia/src/pjmedia/mixer_port.c pjsip/pjmedia/src/pjmedia/mixer_port.c +--- pjproject-2.10/pjmedia/src/pjmedia/mixer_port.c 1970-01-01 01:00:00.000000000 +0100 ++++ pjsip/pjmedia/src/pjmedia/mixer_port.c 2021-02-06 18:45:36.859549796 +0100 +@@ -0,0 +1,141 @@ ++/* ++ * Copyright (C) 2010 AG Projects ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ++ */ ++ ++#include ++ ++#include ++#include ++#include ++#include ++#include ++ ++ ++#define SIGNATURE PJMEDIA_SIG_PORT_MIXER ++#define MIN(a, b) ((a)>(b)?(b):(a)) ++ ++struct mixer_port ++{ ++ pjmedia_port base; ++ pjmedia_frame_type last_frame_type; ++ pj_size_t last_frame_size; ++ pj_timestamp last_frame_timestamp; ++ pj_int16_t* buffer; ++ pj_size_t buffer_size; ++}; ++ ++static pj_status_t mixer_get_frame(pjmedia_port *port, pjmedia_frame *frame); ++static pj_status_t mixer_put_frame(pjmedia_port *port, pjmedia_frame *frame); ++static pj_status_t mixer_on_destroy(pjmedia_port *port); ++ ++ ++PJ_DEF(pj_status_t) pjmedia_mixer_port_create(pj_pool_t *pool, ++ unsigned sampling_rate, ++ unsigned channel_count, ++ unsigned samples_per_frame, ++ unsigned bits_per_sample, ++ pjmedia_port **p_port) ++{ ++ struct mixer_port *port; ++ const pj_str_t name = pj_str("mixer-port"); ++ ++ PJ_ASSERT_RETURN(pool && p_port, PJ_EINVAL); ++ ++ port = PJ_POOL_ZALLOC_T(pool, struct mixer_port); ++ PJ_ASSERT_RETURN(port != NULL, PJ_ENOMEM); ++ ++ pjmedia_port_info_init(&port->base.info, &name, SIGNATURE, sampling_rate, ++ channel_count, bits_per_sample, samples_per_frame); ++ ++ port->base.get_frame = &mixer_get_frame; ++ port->base.put_frame = &mixer_put_frame; ++ port->base.on_destroy = &mixer_on_destroy; ++ port->last_frame_type = PJMEDIA_FRAME_TYPE_NONE; ++ port->last_frame_size = 0; ++ port->last_frame_timestamp.u64 = 0; ++ port->buffer = (pj_int16_t*) pj_pool_calloc(pool, samples_per_frame, sizeof(pj_int16_t)); ++ port->buffer_size = sizeof(pj_int16_t) * samples_per_frame; ++ ++ *p_port = &port->base; ++ ++ return PJ_SUCCESS; ++} ++ ++ ++ ++/* ++ * Put frame to file. ++ */ ++static pj_status_t mixer_put_frame(pjmedia_port *this_port, pjmedia_frame *frame) ++{ ++ struct mixer_port* port = (struct mixer_port*) this_port; ++ ++ if (!frame->size || frame->type != PJMEDIA_FRAME_TYPE_AUDIO) { ++ port->last_frame_type = PJMEDIA_FRAME_TYPE_NONE; ++ port->last_frame_size = 0; ++ port->last_frame_timestamp.u64 = 0; ++ return PJ_SUCCESS; ++ } ++ ++ PJ_ASSERT_RETURN(frame->size <= port->buffer_size, PJ_EINVAL); ++ ++ port->last_frame_type = frame->type; ++ pj_get_timestamp(&port->last_frame_timestamp); ++ port->last_frame_size = MIN(port->buffer_size, frame->size); ++ memcpy(port->buffer, frame->buf, port->last_frame_size); ++ ++ return PJ_SUCCESS; ++} ++ ++ ++/* ++ * Get frame from file. ++ */ ++static pj_status_t mixer_get_frame(pjmedia_port *this_port, pjmedia_frame *frame) ++{ ++ struct mixer_port* port = (struct mixer_port*) this_port; ++ pj_timestamp now; ++ pj_uint32_t frame_age; ++ ++ pj_get_timestamp(&now); ++ frame_age = pj_elapsed_usec(&port->last_frame_timestamp, &now); ++ ++ if (port->last_frame_timestamp.u64 != 0 && frame_age <= 100000) { ++ frame->type = port->last_frame_type; ++ frame->size = port->last_frame_size; ++ frame->timestamp.u64 = 0; ++ if (port->last_frame_size > 0) { ++ memcpy(frame->buf, port->buffer, port->last_frame_size); ++ } ++ } else { ++ frame->type = PJMEDIA_FRAME_TYPE_NONE; ++ frame->size = 0; ++ frame->timestamp.u64 = 0; ++ } ++ ++ return PJ_SUCCESS; ++} ++ ++ ++/* ++ * Destroy port. ++ */ ++static pj_status_t mixer_on_destroy(pjmedia_port *this_port) ++{ ++ return PJ_SUCCESS; ++} ++ +diff -ruN pjproject-2.10/pjmedia/src/pjmedia/rtcp.c pjsip/pjmedia/src/pjmedia/rtcp.c +--- pjproject-2.10/pjmedia/src/pjmedia/rtcp.c 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/pjmedia/src/pjmedia/rtcp.c 2021-02-06 20:30:29.245771690 +0100 +@@ -1003,6 +1003,33 @@ + sess->stat.rx.update_cnt++; + } + ++PJ_DEF(pj_status_t) pjmedia_rtcp_build_rtcp_pli(pjmedia_rtcp_session *session, ++ void *buf, ++ pj_size_t *length) ++{ ++ pjmedia_rtcp_common *hdr; ++ pj_uint8_t *p; ++ pj_size_t len = 12; /* pjmedia_rtcp_common + media SSRC (uint32_t) */ ++ ++ PJ_ASSERT_RETURN(session && buf && length, PJ_EINVAL); ++ ++ /* Verify buffer length */ ++ if (len > *length) ++ return PJ_ETOOSMALL; ++ ++ /* Build RTCP PLI */ ++ hdr = (pjmedia_rtcp_common*)buf; ++ pj_memcpy(hdr, &session->rtcp_sr_pkt.common, sizeof(*hdr)); ++ hdr->pt = RTCP_PSFB; ++ hdr->count = 1; /* FMT: 1 == Picture Loss Indication (PLI) */ ++ hdr->length = pj_htons((pj_uint16_t)(len/4 - 1)); ++ ++ p = (pj_uint8_t*)hdr + sizeof(*hdr); ++ pj_memset(p, 0, (pj_uint8_t*)hdr + len - p); ++ *length = len; ++ return PJ_SUCCESS; ++} ++ + + PJ_DEF(pj_status_t) pjmedia_rtcp_build_rtcp_sdes( + pjmedia_rtcp_session *session, +diff -ruN pjproject-2.10/pjmedia/src/pjmedia/sound_port.c pjsip/pjmedia/src/pjmedia/sound_port.c +--- pjproject-2.10/pjmedia/src/pjmedia/sound_port.c 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/pjmedia/src/pjmedia/sound_port.c 2021-02-06 21:42:52.101118944 +0100 +@@ -98,7 +98,7 @@ + if (snd_port->ec_state) { + if (snd_port->ec_suspended) { + snd_port->ec_suspended = PJ_FALSE; +- //pjmedia_echo_state_reset(snd_port->ec_state); ++ pjmedia_echo_reset(snd_port->ec_state); + PJ_LOG(4,(THIS_FILE, "EC activated")); + } + snd_port->ec_suspend_count = 0; +@@ -312,14 +312,9 @@ + snd_port->aud_param.ec_tail_ms)); + } + +- status = pjmedia_snd_port_set_ec(snd_port, pool, +- snd_port->aud_param.ec_tail_ms, +- snd_port->prm_ec_options); +- if (status != PJ_SUCCESS) { +- pjmedia_aud_stream_destroy(snd_port->aud_stream); +- snd_port->aud_stream = NULL; +- return status; +- } ++ pjmedia_snd_port_set_ec(snd_port, pool, ++ snd_port->aud_param.ec_tail_ms, ++ snd_port->prm_ec_options); + } + + /* Start sound stream. */ +@@ -550,6 +545,17 @@ + } + + ++/* Reset EC state */ ++PJ_DEF(pj_status_t) pjmedia_snd_port_reset_ec_state( pjmedia_snd_port *snd_port ) ++{ ++ PJ_ASSERT_RETURN(snd_port, PJ_EINVAL); ++ if (snd_port->ec_state) { ++ pjmedia_echo_reset(snd_port->ec_state); ++ PJ_LOG(4,(THIS_FILE, "EC reset")); ++ } ++ return PJ_SUCCESS; ++} ++ + /* + * Change EC settings. + */ +diff -ruN pjproject-2.10/pjmedia/src/pjmedia/transport_ice.c pjsip/pjmedia/src/pjmedia/transport_ice.c +--- pjproject-2.10/pjmedia/src/pjmedia/transport_ice.c 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/pjmedia/src/pjmedia/transport_ice.c 2021-02-06 21:40:50.021656143 +0100 +@@ -163,6 +163,10 @@ + pj_ice_strans_op op, + pj_status_t status); + ++static void ice_on_ice_state(pj_ice_strans *ice_st, ++ pj_ice_strans_state prev, ++ pj_ice_strans_state curr); ++ + /* + * Clean up ICE resources. + */ +@@ -280,6 +284,7 @@ + /* Configure ICE callbacks */ + pj_bzero(&ice_st_cb, sizeof(ice_st_cb)); + ice_st_cb.on_ice_complete = &ice_on_ice_complete; ++ ice_st_cb.on_ice_state = &ice_on_ice_state; + ice_st_cb.on_rx_data = &ice_on_rx_data; + + /* Configure RTP socket buffer settings, if not set */ +@@ -314,6 +319,17 @@ + return PJ_SUCCESS; + } + ++/* ++ * Get the ICE stream transport associated with this media transport. ++ */ ++PJ_DEF(pj_ice_strans*) pjmedia_ice_get_strans(pjmedia_transport *tp) ++{ ++ struct transport_ice *tp_ice; ++ ++ tp_ice = (struct transport_ice*) tp; ++ return tp_ice->ice_st; ++} ++ + PJ_DEF(pj_grp_lock_t *) pjmedia_ice_get_grp_lock(pjmedia_transport *tp) + { + PJ_ASSERT_RETURN(tp, NULL); +@@ -403,6 +419,10 @@ + "Stopping ICE, reason=%s", reason)); + } + ++ /* Notify application about ICE stop */ ++ if (tp_ice->cb.on_ice_stop) ++ (*tp_ice->cb.on_ice_stop)(&tp_ice->base, (char *)reason, err); ++ + if (tp_ice->ice_st) { + pj_ice_strans_stop_ice(tp_ice->ice_st); + } +@@ -1745,17 +1765,17 @@ + if (status != PJ_SUCCESS) + return status; + +- pj_sockaddr_cp(&info->sock_info.rtp_addr_name, &cand.addr); ++ pj_sockaddr_cp(&info->sock_info.rtp_addr_name, &cand.base_addr); + + /* Get RTCP default address */ + if (tp_ice->use_rtcp_mux) { +- pj_sockaddr_cp(&info->sock_info.rtcp_addr_name, &cand.addr); ++ pj_sockaddr_cp(&info->sock_info.rtcp_addr_name, &cand.base_addr); + } else if (tp_ice->comp_cnt > 1) { + status = pj_ice_strans_get_def_cand(tp_ice->ice_st, 2, &cand); + if (status != PJ_SUCCESS) + return status; + +- pj_sockaddr_cp(&info->sock_info.rtcp_addr_name, &cand.addr); ++ pj_sockaddr_cp(&info->sock_info.rtcp_addr_name, &cand.base_addr); + } + + /* Set remote address originating RTP & RTCP if this transport has +@@ -2115,6 +2135,19 @@ + } + } + ++static void ice_on_ice_state(pj_ice_strans *ice_st, ++ pj_ice_strans_state prev, ++ pj_ice_strans_state curr) ++{ ++ struct transport_ice *tp_ice; ++ ++ tp_ice = (struct transport_ice*) pj_ice_strans_get_user_data(ice_st); ++ ++ /* Notify application */ ++ if (tp_ice->cb.on_ice_state) ++ (*tp_ice->cb.on_ice_state)(&tp_ice->base, prev, curr); ++} ++ + + /* Simulate lost */ + static pj_status_t transport_simulate_lost(pjmedia_transport *tp, +diff -ruN pjproject-2.10/pjmedia/src/pjmedia/transport_zrtp.c pjsip/pjmedia/src/pjmedia/transport_zrtp.c +--- pjproject-2.10/pjmedia/src/pjmedia/transport_zrtp.c 1970-01-01 01:00:00.000000000 +0100 ++++ pjsip/pjmedia/src/pjmedia/transport_zrtp.c 2021-02-06 16:42:04.322625052 +0100 +@@ -0,0 +1,1261 @@ ++/* $Id$ */ ++/* ++ * Copyright (C) 2010 Werner Dittmann ++ * This is the pjmedia ZRTP transport module. ++ * ++ * 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 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ++ */ ++ ++#include ++#include ++#include ++#include ++ ++#include "../../third_party/zsrtp/include/ZsrtpCWrapper.h" ++ ++#define THIS_FILE "transport_zrtp.c" ++ ++#define MAX_RTP_BUFFER_LEN PJMEDIA_MAX_MTU ++#define MAX_RTCP_BUFFER_LEN PJMEDIA_MAX_MTU ++ ++ ++/* Transport functions prototypes */ ++static pj_status_t transport_get_info(pjmedia_transport *tp, ++ pjmedia_transport_info *info); ++static pj_status_t transport_attach(pjmedia_transport *tp, ++ void *user_data, ++ const pj_sockaddr_t *rem_addr, ++ const pj_sockaddr_t *rem_rtcp, ++ unsigned addr_len, ++ void (*rtp_cb)(void*, ++ void*, ++ pj_ssize_t), ++ void (*rtcp_cb)(void*, ++ void*, ++ pj_ssize_t)); ++static void transport_detach(pjmedia_transport *tp, ++ void *strm); ++static pj_status_t transport_send_rtp(pjmedia_transport *tp, ++ const void *pkt, ++ pj_size_t size); ++static pj_status_t transport_send_rtcp(pjmedia_transport *tp, ++ const void *pkt, ++ pj_size_t size); ++static pj_status_t transport_send_rtcp2(pjmedia_transport *tp, ++ const pj_sockaddr_t *addr, ++ unsigned addr_len, ++ const void *pkt, ++ pj_size_t size); ++static pj_status_t transport_media_create(pjmedia_transport *tp, ++ pj_pool_t *sdp_pool, ++ unsigned options, ++ const pjmedia_sdp_session *rem_sdp, ++ unsigned media_index); ++static pj_status_t transport_encode_sdp(pjmedia_transport *tp, ++ pj_pool_t *sdp_pool, ++ pjmedia_sdp_session *local_sdp, ++ const pjmedia_sdp_session *rem_sdp, ++ unsigned media_index); ++static pj_status_t transport_media_start(pjmedia_transport *tp, ++ pj_pool_t *pool, ++ const pjmedia_sdp_session *local_sdp, ++ const pjmedia_sdp_session *rem_sdp, ++ unsigned media_index); ++static pj_status_t transport_media_stop(pjmedia_transport *tp); ++static pj_status_t transport_simulate_lost(pjmedia_transport *tp, ++ pjmedia_dir dir, ++ unsigned pct_lost); ++static pj_status_t transport_destroy(pjmedia_transport *tp); ++ ++ ++/* The transport operations */ ++static struct pjmedia_transport_op tp_zrtp_op = ++{ ++ &transport_get_info, ++ &transport_attach, ++ &transport_detach, ++ &transport_send_rtp, ++ &transport_send_rtcp, ++ &transport_send_rtcp2, ++ &transport_media_create, ++ &transport_encode_sdp, ++ &transport_media_start, ++ &transport_media_stop, ++ &transport_simulate_lost, ++ &transport_destroy ++}; ++ ++/* The transport zrtp instance */ ++struct tp_zrtp ++{ ++ pjmedia_transport base; ++ pj_pool_t *pool; ++ ++ /* Stream information. */ ++ void *stream_user_data; ++ void (*stream_rtp_cb)(void *user_data, ++ void *pkt, ++ pj_ssize_t); ++ void (*stream_rtcp_cb)(void *user_data, ++ void *pkt, ++ pj_ssize_t); ++ ++ /* Add your own member here.. */ ++ uint64_t protect; ++ uint64_t unprotect; ++ int32_t unprotect_err; ++ int32_t refcount; ++ pj_timer_heap_t* timer_heap; ++ pj_timer_entry timeoutEntry; ++ pj_mutex_t* zrtpMutex; ++ ZsrtpContext* srtpReceive; ++ ZsrtpContext* srtpSend; ++ ZsrtpContextCtrl* srtcpReceive; ++ ZsrtpContextCtrl* srtcpSend; ++ void* sendBuffer; ++ void* sendBufferCtrl; ++ pj_uint8_t* zrtpBuffer; ++// pj_int32_t sendBufferLen; ++ pj_uint32_t peerSSRC; /* stored in host order */ ++ pj_uint32_t localSSRC; /* stored in host order */ ++ char* clientIdString; ++ pjmedia_transport *slave_tp; ++ pjmedia_zrtp_cb cb; ++ ZrtpContext* zrtpCtx; ++ pj_uint16_t zrtpSeq; ++ pj_bool_t enableZrtp; ++ pj_bool_t started; ++ pj_bool_t close_slave; ++ pj_bool_t mitmMode; ++ char cipher[128]; ++}; ++ ++/* Forward declaration of thethe ZRTP specific callback functions that this ++ adapter must implement */ ++static int32_t zrtp_sendDataZRTP(ZrtpContext* ctx, const uint8_t* data, int32_t length) ; ++static int32_t zrtp_activateTimer(ZrtpContext* ctx, int32_t time) ; ++static int32_t zrtp_cancelTimer(ZrtpContext* ctx) ; ++static void zrtp_sendInfo(ZrtpContext* ctx, int32_t severity, int32_t subCode) ; ++static int32_t zrtp_srtpSecretsReady(ZrtpContext* ctx, C_SrtpSecret_t* secrets, int32_t part) ; ++static void zrtp_srtpSecretsOff(ZrtpContext* ctx, int32_t part) ; ++static void zrtp_srtpSecretsOn(ZrtpContext* ctx, char* c, char* s, int32_t verified) ; ++static void zrtp_handleGoClear(ZrtpContext* ctx) ; ++static void zrtp_zrtpNegotiationFailed(ZrtpContext* ctx, int32_t severity, int32_t subCode) ; ++static void zrtp_zrtpNotSuppOther(ZrtpContext* ctx) ; ++static void zrtp_synchEnter(ZrtpContext* ctx) ; ++static void zrtp_synchLeave(ZrtpContext* ctx) ; ++static void zrtp_zrtpAskEnrollment(ZrtpContext* ctx, int32_t info) ; ++static void zrtp_zrtpInformEnrollment(ZrtpContext* ctx, int32_t info) ; ++static void zrtp_signSAS(ZrtpContext* ctx, uint8_t* sasHash) ; ++static int32_t zrtp_checkSASSignature(ZrtpContext* ctx, uint8_t* sasHash) ; ++ ++/* The callback function structure for ZRTP */ ++static zrtp_Callbacks c_callbacks = ++{ ++ &zrtp_sendDataZRTP, ++ &zrtp_activateTimer, ++ &zrtp_cancelTimer, ++ &zrtp_sendInfo, ++ &zrtp_srtpSecretsReady, ++ &zrtp_srtpSecretsOff, ++ &zrtp_srtpSecretsOn, ++ &zrtp_handleGoClear, ++ &zrtp_zrtpNegotiationFailed, ++ &zrtp_zrtpNotSuppOther, ++ &zrtp_synchEnter, ++ &zrtp_synchLeave, ++ &zrtp_zrtpAskEnrollment, ++ &zrtp_zrtpInformEnrollment, ++ &zrtp_signSAS, ++ &zrtp_checkSASSignature ++}; ++ ++static void timer_callback(pj_timer_heap_t *ht, pj_timer_entry *e); ++ ++static char clientId[] = "SIP SIMPLE Client SDK"; ++ ++/* ++ * Create the ZRTP transport. ++ */ ++PJ_DEF(pj_status_t) pjmedia_transport_zrtp_create(pjmedia_endpt *endpt, ++ pj_timer_heap_t *timer_heap, ++ pjmedia_transport *tp, ++ pjmedia_transport **p_tp, ++ pj_bool_t close_slave) ++{ ++ pj_pool_t *pool; ++ struct tp_zrtp *zrtp; ++ ++ PJ_ASSERT_RETURN(endpt && tp && p_tp, PJ_EINVAL); ++ ++ /* Create the pool and initialize the adapter structure */ ++ pool = pjmedia_endpt_create_pool(endpt, "zrtp%p", 5*1024, 512); ++ zrtp = PJ_POOL_ZALLOC_T(pool, struct tp_zrtp); ++ zrtp->pool = pool; ++ ++ /* Initialize base pjmedia_transport */ ++ pj_memcpy(zrtp->base.name, pool->obj_name, PJ_MAX_OBJ_NAME); ++ zrtp->base.type = tp->type; ++ zrtp->base.op = &tp_zrtp_op; ++ ++ /* Set the timer heap to be used for timers */ ++ zrtp->timer_heap = timer_heap; ++ ++ /* Create the empty wrapper */ ++ zrtp->zrtpCtx = zrtp_CreateWrapper(); ++ ++ /* Initialize standard values */ ++ zrtp->clientIdString = clientId; /* Set standard name */ ++ zrtp->zrtpSeq = 1; /* TODO: randomize */ ++ pj_mutex_create_simple(zrtp->pool, "zrtp", &zrtp->zrtpMutex); ++ zrtp->zrtpBuffer = pj_pool_zalloc(pool, MAX_ZRTP_SIZE); ++ zrtp->sendBuffer = pj_pool_zalloc(pool, MAX_RTP_BUFFER_LEN); ++ zrtp->sendBufferCtrl = pj_pool_zalloc(pool, MAX_RTCP_BUFFER_LEN); ++ ++ zrtp->slave_tp = tp; ++ zrtp->close_slave = close_slave; ++ zrtp->mitmMode = PJ_FALSE; ++ ++ /* Done */ ++ zrtp->refcount++; ++ *p_tp = &zrtp->base; ++ return PJ_SUCCESS; ++} ++ ++PJ_DECL(pj_status_t) pjmedia_transport_zrtp_initialize(pjmedia_transport *tp, ++ const char *zidFilename, ++ pj_bool_t autoEnable, ++ pjmedia_zrtp_cb *cb) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; ++ PJ_ASSERT_RETURN(tp, PJ_EINVAL); ++ ++ zrtp_initializeZrtpEngine(zrtp->zrtpCtx, &c_callbacks, zrtp->clientIdString, ++ zidFilename, zrtp, zrtp->mitmMode); ++ zrtp->enableZrtp = autoEnable; ++ if (cb) ++ pj_memcpy(&zrtp->cb, cb, sizeof(pjmedia_zrtp_cb)); ++ return PJ_SUCCESS; ++} ++ ++static void timer_callback(pj_timer_heap_t *ht, pj_timer_entry *e) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)e->user_data; ++ ++ zrtp_processTimeout(zrtp->zrtpCtx); ++ PJ_UNUSED_ARG(ht); ++} ++ ++/* ++ * Here start with callback functions that support the ZRTP core ++ */ ++static int32_t zrtp_sendDataZRTP(ZrtpContext* ctx, const uint8_t* data, int32_t length) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; ++ pj_uint16_t totalLen = length + 12; /* Fixed number of bytes of ZRTP header */ ++ pj_uint32_t crc; ++ pj_uint8_t* buffer = zrtp->zrtpBuffer; ++ pj_uint16_t* pus; ++ pj_uint32_t* pui; ++ ++ if ((totalLen) > MAX_ZRTP_SIZE) ++ return 0; ++ ++ /* Get some handy pointers */ ++ pus = (pj_uint16_t*)buffer; ++ pui = (pj_uint32_t*)buffer; ++ ++ /* set up fixed ZRTP header */ ++ *buffer = 0x10; /* invalid RTP version - refer to ZRTP spec chap 5 */ ++ *(buffer + 1) = 0; ++ pus[1] = pj_htons(zrtp->zrtpSeq++); ++ pui[1] = pj_htonl(ZRTP_MAGIC); ++ pui[2] = pj_htonl(zrtp->localSSRC); /* stored in host order */ ++ ++ /* Copy ZRTP message data behind the header data */ ++ pj_memcpy(buffer+12, data, length); ++ ++ /* Setup and compute ZRTP CRC */ ++ crc = zrtp_GenerateCksum(buffer, totalLen-CRC_SIZE); ++ ++ /* convert and store CRC in ZRTP packet.*/ ++ crc = zrtp_EndCksum(crc); ++ *(uint32_t*)(buffer+totalLen-CRC_SIZE) = pj_htonl(crc); ++ ++ /* Send the ZRTP packet using the slave transport */ ++ return (pjmedia_transport_send_rtp(zrtp->slave_tp, buffer, totalLen) == PJ_SUCCESS) ? 1 : 0; ++} ++ ++static int32_t zrtp_activateTimer(ZrtpContext* ctx, int32_t time) ++{ ++ pj_time_val timeout; ++ pj_status_t status; ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; ++ ++ timeout.sec = time / 1000; ++ timeout.msec = time % 1000; ++ ++ pj_timer_entry_init(&zrtp->timeoutEntry, 0, zrtp, &timer_callback); ++ status = pj_timer_heap_schedule(zrtp->timer_heap, &zrtp->timeoutEntry, &timeout); ++ if (status == PJ_SUCCESS) ++ return 1; ++ else ++ return 0; ++} ++ ++static int32_t zrtp_cancelTimer(ZrtpContext* ctx) ++{ ++ pj_status_t status; ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; ++ ++ status = pj_timer_heap_cancel(zrtp->timer_heap, &zrtp->timeoutEntry); ++ if (status == PJ_SUCCESS) ++ return 1; ++ else ++ return 0; ++} ++ ++static void zrtp_sendInfo(ZrtpContext* ctx, int32_t severity, int32_t subCode) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; ++ ++ if (zrtp->cb.show_message) ++ zrtp->cb.show_message(&zrtp->base, severity, subCode); ++ ++} ++ ++static int32_t zrtp_srtpSecretsReady(ZrtpContext* ctx, C_SrtpSecret_t* secrets, int32_t part) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; ++ ++ ZsrtpContext* recvCrypto; ++ ZsrtpContext* senderCrypto; ++ ZsrtpContextCtrl* recvCryptoCtrl; ++ ZsrtpContextCtrl* senderCryptoCtrl; ++ int cipher; ++ int authn; ++ int authKeyLen; ++ // int srtcpAuthTagLen; ++ ++ if (secrets->authAlgorithm == zrtp_Sha1) { ++ authn = SrtpAuthenticationSha1Hmac; ++ authKeyLen = 20; ++ // srtcpAuthTagLen = 80; // Always 80 bit for SRTCP / SHA1 ++ } ++ ++ if (secrets->authAlgorithm == zrtp_Skein) { ++ authn = SrtpAuthenticationSkeinHmac; ++ authKeyLen = 32; ++ // srtcpAuthTagLen = 64; // Always 64 bit for SRTCP / Skein ++ } ++ ++ if (secrets->symEncAlgorithm == zrtp_Aes) ++ cipher = SrtpEncryptionAESCM; ++ ++ if (secrets->symEncAlgorithm == zrtp_TwoFish) ++ cipher = SrtpEncryptionTWOCM; ++ ++ if (part == ForSender) { ++ // To encrypt packets: intiator uses initiator keys, ++ // responder uses responder keys ++ // Create a "half baked" crypto context first and store it. This is ++ // the main crypto context for the sending part of the connection. ++ if (secrets->role == Initiator) { ++ senderCrypto = zsrtp_CreateWrapper(zrtp->localSSRC, ++ 0, ++ 0L, // keyderivation << 48, ++ cipher, // encryption algo ++ authn, // authtentication algo ++ (unsigned char*)secrets->keyInitiator, // Master Key ++ secrets->initKeyLen / 8, // Master Key length ++ (unsigned char*)secrets->saltInitiator, // Master Salt ++ secrets->initSaltLen / 8, // Master Salt length ++ secrets->initKeyLen / 8, // encryption keyl ++ authKeyLen, // authentication key len ++ secrets->initSaltLen / 8, // session salt len ++ secrets->srtpAuthTagLen / 8); // authentication tag lenA ++ ++ senderCryptoCtrl = zsrtp_CreateWrapperCtrl(zrtp->localSSRC, ++ cipher, // encryption algo ++ authn, // authtication algo ++ (unsigned char*)secrets->keyInitiator, // Master Key ++ secrets->initKeyLen / 8, // Master Key length ++ (unsigned char*)secrets->saltInitiator, // Master Salt ++ secrets->initSaltLen / 8, // Master Salt length ++ secrets->initKeyLen / 8, // encryption keyl ++ authKeyLen, // authentication key len ++ secrets->initSaltLen / 8, // session salt len ++ secrets->srtpAuthTagLen / 8); // authentication tag len ++ // srtcpAuthTagLen / 8); // authentication tag len ++ } ++ else { ++ senderCrypto = zsrtp_CreateWrapper(zrtp->localSSRC, ++ 0, ++ 0L, // keyderivation << 48, ++ cipher, // encryption algo ++ authn, // authtentication algo ++ (unsigned char*)secrets->keyResponder, // Master Key ++ secrets->respKeyLen / 8, // Master Key length ++ (unsigned char*)secrets->saltResponder, // Master Salt ++ secrets->respSaltLen / 8, // Master Salt length ++ secrets->respKeyLen / 8, // encryption keyl ++ authKeyLen, // authentication key len ++ secrets->respSaltLen / 8, // session salt len ++ secrets->srtpAuthTagLen / 8); // authentication tag len ++ ++ senderCryptoCtrl = zsrtp_CreateWrapperCtrl(zrtp->localSSRC, ++ cipher, // encryption algo ++ authn, // authtication algo ++ (unsigned char*)secrets->keyResponder, // Master Key ++ secrets->respKeyLen / 8, // Master Key length ++ (unsigned char*)secrets->saltResponder, // Master Salt ++ secrets->respSaltLen / 8, // Master Salt length ++ secrets->respKeyLen / 8, // encryption keyl ++ authKeyLen, // authentication key len ++ secrets->respSaltLen / 8, // session salt len ++ secrets->srtpAuthTagLen / 8); // authentication tag len ++ // srtcpAuthTagLen / 8); // authentication tag len ++ } ++ if (senderCrypto == NULL) { ++ return 0; ++ } ++ // Create a SRTP crypto context for real SSRC sender stream. ++ // Note: key derivation can be done at this time only if the ++ // key derivation rate is 0 (disabled). For ZRTP this is the ++ // case: the key derivation is defined as 2^48 ++ // which is effectively 0. ++ zsrtp_deriveSrtpKeys(senderCrypto, 0L); ++ zrtp->srtpSend = senderCrypto; ++ ++ zsrtp_deriveSrtpKeysCtrl(senderCryptoCtrl); ++ zrtp->srtcpSend = senderCryptoCtrl; ++ } ++ if (part == ForReceiver) { ++ // To decrypt packets: intiator uses responder keys, ++ // responder initiator keys ++ // See comment above. ++ if (secrets->role == Initiator) { ++ recvCrypto = zsrtp_CreateWrapper(zrtp->peerSSRC, ++ 0, ++ 0L, // keyderivation << 48, ++ cipher, // encryption algo ++ authn, // authtentication algo ++ (unsigned char*)secrets->keyResponder, // Master Key ++ secrets->respKeyLen / 8, // Master Key length ++ (unsigned char*)secrets->saltResponder, // Master Salt ++ secrets->respSaltLen / 8, // Master Salt length ++ secrets->respKeyLen / 8, // encryption keyl ++ authKeyLen, // authentication key len ++ secrets->respSaltLen / 8, // session salt len ++ secrets->srtpAuthTagLen / 8); // authentication tag len ++ ++ recvCryptoCtrl = zsrtp_CreateWrapperCtrl(zrtp->peerSSRC, ++ cipher, // encryption algo ++ authn, // authtication algo ++ (unsigned char*)secrets->keyResponder, // Master Key ++ secrets->respKeyLen / 8, // Master Key length ++ (unsigned char*)secrets->saltResponder, // Master Salt ++ secrets->respSaltLen / 8, // Master Salt length ++ secrets->respKeyLen / 8, // encryption keyl ++ authKeyLen, // authentication key len ++ secrets->respSaltLen / 8, // session salt len ++ secrets->srtpAuthTagLen / 8); // authentication tag len ++ // srtcpAuthTagLen / 8); // authentication tag len ++ } ++ else { ++ recvCrypto = zsrtp_CreateWrapper(zrtp->peerSSRC, ++ 0, ++ 0L, // keyderivation << 48, ++ cipher, // encryption algo ++ authn, // authtentication algo ++ (unsigned char*)secrets->keyInitiator, // Master Key ++ secrets->initKeyLen / 8, // Master Key length ++ (unsigned char*)secrets->saltInitiator, // Master Salt ++ secrets->initSaltLen / 8, // Master Salt length ++ secrets->initKeyLen / 8, // encryption keyl ++ authKeyLen, // authentication key len ++ secrets->initSaltLen / 8, // session salt len ++ secrets->srtpAuthTagLen / 8); // authentication tag len ++ ++ recvCryptoCtrl = zsrtp_CreateWrapperCtrl(zrtp->peerSSRC, ++ cipher, // encryption algo ++ authn, // authtication algo ++ (unsigned char*)secrets->keyInitiator, // Master Key ++ secrets->initKeyLen / 8, // Master Key length ++ (unsigned char*)secrets->saltInitiator, // Master Salt ++ secrets->initSaltLen / 8, // Master Salt length ++ secrets->initKeyLen / 8, // encryption keyl ++ authKeyLen, // authentication key len ++ secrets->initSaltLen / 8, // session salt len ++ secrets->srtpAuthTagLen / 8); // authentication tag len ++ // srtcpAuthTagLen / 8); // authentication tag len ++ } ++ if (recvCrypto == NULL) { ++ return 0; ++ } ++ // Create a SRTP crypto context for real SSRC input stream. ++ // If the sender didn't provide a SSRC just insert the template ++ // into the queue. After we received the first packet the real ++ // crypto context will be created. ++ // ++ // Note: key derivation can be done at this time only if the ++ // key derivation rate is 0 (disabled). For ZRTP this is the ++ // case: the key derivation is defined as 2^48 ++ // which is effectively 0. ++ zsrtp_deriveSrtpKeys(recvCrypto, 0L); ++ zrtp->srtpReceive = recvCrypto; ++ ++ zsrtp_deriveSrtpKeysCtrl(recvCryptoCtrl); ++ zrtp->srtcpReceive = recvCryptoCtrl; ++ } ++ return 1; ++} ++ ++static void zrtp_srtpSecretsOff(ZrtpContext* ctx, int32_t part) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; ++ ++ if (part == ForSender) ++ { ++ zsrtp_DestroyWrapper(zrtp->srtpSend); ++ zsrtp_DestroyWrapperCtrl(zrtp->srtcpSend); ++ zrtp->srtpSend = NULL; ++ zrtp->srtcpSend = NULL; ++ } ++ if (part == ForReceiver) ++ { ++ zsrtp_DestroyWrapper(zrtp->srtpReceive); ++ zsrtp_DestroyWrapperCtrl(zrtp->srtcpReceive); ++ zrtp->srtpReceive = NULL; ++ zrtp->srtcpReceive = NULL; ++ } ++ ++ if (zrtp->cb.secure_off) ++ zrtp->cb.secure_off(&zrtp->base); ++} ++ ++static void zrtp_srtpSecretsOn(ZrtpContext* ctx, char* c, char* s, int32_t verified) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; ++ int len; ++ ++ len = strlen(c); ++ if (len > sizeof(zrtp->cipher) - 1) ++ len = sizeof(zrtp->cipher) - 1; ++ memcpy(zrtp->cipher, c, len); ++ zrtp->cipher[len] = '\0'; ++ ++ if (zrtp->cb.secure_on) ++ zrtp->cb.secure_on(&zrtp->base, c); ++ ++ if (s && strlen(s) > 0 && zrtp->cb.show_sas) ++ zrtp->cb.show_sas(&zrtp->base, s, verified); ++} ++ ++static void zrtp_handleGoClear(ZrtpContext* ctx) ++{ ++ /* TODO: implement */ ++} ++ ++static void zrtp_zrtpNegotiationFailed(ZrtpContext* ctx, int32_t severity, int32_t subCode) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; ++ ++ if (zrtp->cb.negotiation_failed) ++ zrtp->cb.negotiation_failed(&zrtp->base, severity, subCode); ++} ++ ++static void zrtp_zrtpNotSuppOther(ZrtpContext* ctx) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; ++ ++ if (zrtp->cb.not_supported_by_other) ++ zrtp->cb.not_supported_by_other(&zrtp->base); ++} ++ ++static void zrtp_synchEnter(ZrtpContext* ctx) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; ++ pj_mutex_lock(zrtp->zrtpMutex); ++} ++ ++static void zrtp_synchLeave(ZrtpContext* ctx) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; ++ pj_mutex_unlock(zrtp->zrtpMutex); ++} ++ ++static void zrtp_zrtpAskEnrollment(ZrtpContext* ctx, int32_t info) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; ++ ++ if (zrtp->cb.ask_enrollment) ++ zrtp->cb.ask_enrollment(&zrtp->base, info); ++} ++ ++static void zrtp_zrtpInformEnrollment(ZrtpContext* ctx, int32_t info) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; ++ ++ if (zrtp->cb.inform_enrollment) ++ zrtp->cb.inform_enrollment(&zrtp->base, info); ++} ++ ++static void zrtp_signSAS(ZrtpContext* ctx, uint8_t* sasHash) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; ++ ++ if (zrtp->cb.sign_sas) ++ zrtp->cb.sign_sas(&zrtp->base, sasHash); ++} ++ ++static int32_t zrtp_checkSASSignature(ZrtpContext* ctx, uint8_t* sasHash) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)ctx->userData; ++ ++ if (zrtp->cb.check_sas_signature) ++ return zrtp->cb.check_sas_signature(&zrtp->base, sasHash); ++ return 0; ++} ++ ++/* ++ * Implement the specific ZRTP transport functions ++ */ ++PJ_DEF(void) pjmedia_transport_zrtp_setEnableZrtp(pjmedia_transport *tp, pj_bool_t onOff) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; ++ pj_assert(tp); ++ ++ zrtp->enableZrtp = onOff; ++} ++ ++PJ_DEF(pj_bool_t) pjmedia_transport_zrtp_isEnableZrtp(pjmedia_transport *tp) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; ++ PJ_ASSERT_RETURN(tp, PJ_FALSE); ++ ++ return zrtp->enableZrtp; ++ ++} ++ ++PJ_DEF(void) pjmedia_transport_zrtp_startZrtp(pjmedia_transport *tp) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; ++ pj_assert(tp && zrtp->zrtpCtx); ++ ++ zrtp_startZrtpEngine(zrtp->zrtpCtx); ++ zrtp->started = 1; ++} ++ ++PJ_DEF(void) pjmedia_transport_zrtp_stopZrtp(pjmedia_transport *tp) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; ++ pj_assert(tp && zrtp->zrtpCtx); ++ ++ zrtp_stopZrtpEngine(zrtp->zrtpCtx); ++ zrtp->started = 0; ++} ++ ++PJ_DEF(void) pjmedia_transport_zrtp_setLocalSSRC(pjmedia_transport *tp, uint32_t ssrc) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; ++ pj_assert(tp); ++ ++ zrtp->localSSRC = ssrc; ++} ++ ++PJ_DEF(pj_bool_t) pjmedia_transport_zrtp_isMitmMode(pjmedia_transport *tp) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; ++ pj_assert(tp); ++ ++ return zrtp->mitmMode; ++} ++ ++PJ_DEF(void) pjmedia_transport_zrtp_setMitmMode(pjmedia_transport *tp, pj_bool_t mitmMode) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; ++ pj_assert(tp); ++ ++ zrtp->mitmMode = mitmMode; ++} ++ ++PJ_DEF(ZrtpContext*) pjmedia_transport_zrtp_getZrtpContext(pjmedia_transport *tp) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; ++ PJ_ASSERT_RETURN(tp, NULL); ++ ++ return zrtp->zrtpCtx; ++} ++ ++PJ_DEF(void) pjmedia_transport_zrtp_setSASVerified(pjmedia_transport *tp, pj_bool_t verified) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; ++ pj_assert(tp); ++ ++ if (verified) ++ zrtp_SASVerified(zrtp->zrtpCtx); ++ else ++ zrtp_resetSASVerified(zrtp->zrtpCtx); ++} ++ ++PJ_DEF(int) pjmedia_transport_zrtp_getPeerZid(pjmedia_transport *tp, unsigned char* data) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; ++ pj_assert(tp); ++ ++ return zrtp_getPeerZid(zrtp->zrtpCtx, data); ++} ++ ++PJ_DEF(char*) pjmedia_transport_zrtp_getPeerName(pjmedia_transport *tp) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; ++ pj_assert(tp); ++ ++ return zrtp_getPeerName(zrtp->zrtpCtx); ++} ++ ++PJ_DEF(void) pjmedia_transport_zrtp_putPeerName(pjmedia_transport *tp, const char *name) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; ++ pj_assert(tp); ++ ++ zrtp_putPeerName(zrtp->zrtpCtx, name); ++} ++ ++PJ_DEF(char*) pjmedia_transport_zrtp_getMultiStreamParameters(pjmedia_transport *tp, pj_int32_t *length) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; ++ pj_assert(tp); ++ ++ return zrtp_getMultiStrParams(zrtp->zrtpCtx, length); ++} ++ ++PJ_DEF(void) pjmedia_transport_zrtp_setMultiStreamParameters(pjmedia_transport *tp, const char *parameters, pj_int32_t length, pjmedia_transport *master_tp) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; ++ struct tp_zrtp *master_zrtp = (struct tp_zrtp*)master_tp; ++ pj_assert(tp); ++ pj_assert(master_tp); ++ ++ zrtp_setMultiStrParams(zrtp->zrtpCtx, (char*) parameters, length, master_zrtp->zrtpCtx); ++} ++ ++/* ++ * get_info() is called to get the transport addresses to be put ++ * in SDP c= line and a=rtcp line. ++ */ ++static pj_status_t transport_get_info(pjmedia_transport *tp, ++ pjmedia_transport_info *info) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; ++ pjmedia_zrtp_info zrtp_info; ++ int spc_info_idx; ++ ++ PJ_ASSERT_RETURN(tp && info, PJ_EINVAL); ++ PJ_ASSERT_RETURN(info->specific_info_cnt < ++ PJMEDIA_TRANSPORT_SPECIFIC_INFO_MAXCNT, PJ_ETOOMANY); ++ ++ zrtp_info.active = zrtp_inState(zrtp->zrtpCtx, SecureState) ? PJ_TRUE : PJ_FALSE; ++ if (zrtp_info.active) ++ memcpy(zrtp_info.cipher, zrtp->cipher, sizeof(zrtp->cipher)); ++ else ++ zrtp_info.cipher[0] = '\0'; ++ ++ spc_info_idx = info->specific_info_cnt++; ++ info->spc_info[spc_info_idx].type = PJMEDIA_TRANSPORT_TYPE_ZRTP; ++ ++ pj_memcpy(&info->spc_info[spc_info_idx].buffer, &zrtp_info, ++ sizeof(zrtp_info)); ++ ++ return pjmedia_transport_get_info(zrtp->slave_tp, info); ++} ++ ++/* This is our RTP callback, that is called by the slave transport when it ++ * receives RTP packet. ++ */ ++static void transport_rtp_cb(void *user_data, void *pkt, pj_ssize_t size) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)user_data; ++ ++ pj_uint8_t* buffer = (pj_uint8_t*)pkt; ++ int32_t newLen = 0; ++ pj_status_t rc = PJ_SUCCESS; ++ ++ pj_assert(zrtp && zrtp->stream_rtcp_cb && pkt); ++ ++ // check if this could be a real RTP/SRTP packet. ++ if ((*buffer & 0xf0) != 0x10) ++ { ++ // Could be real RTP, check if we are in secure mode ++ if (zrtp->srtpReceive == NULL || size < 0) ++ { ++ zrtp->stream_rtp_cb(zrtp->stream_user_data, pkt, size); ++ } ++ else ++ { ++ rc = zsrtp_unprotect(zrtp->srtpReceive, pkt, size, &newLen); ++ if (rc == 1) ++ { ++ zrtp->unprotect++; ++ zrtp->stream_rtp_cb(zrtp->stream_user_data, pkt, ++ newLen); ++ zrtp->unprotect_err = 0; ++ } ++ else ++ { ++ if (zrtp->cb.show_message) ++ { ++ if (rc == -1) ++ zrtp->cb.show_message(&zrtp->base, zrtp_Warning, zrtp_WarningSRTPauthError); ++ else ++ zrtp->cb.show_message(&zrtp->base, zrtp_Warning, zrtp_WarningSRTPreplayError); ++ } ++ zrtp->unprotect_err = rc; ++ /* We failed to decrypt the packet, but forward it regardless to the slave ++ * transport, it might not have been encrypted after all */ ++ zrtp->stream_rtp_cb(zrtp->stream_user_data, pkt, size); ++ } ++ } ++ if (!zrtp->started && zrtp->enableZrtp) ++ pjmedia_transport_zrtp_startZrtp((pjmedia_transport *)zrtp); ++ ++ return; ++ } ++ ++ // We assume all other packets are ZRTP packets here. Process ++ // if ZRTP processing is enabled. Because valid RTP packets are ++ // already handled we delete any packets here after processing. ++ if (zrtp->enableZrtp && zrtp->zrtpCtx != NULL) ++ { ++ // Get CRC value into crc (see above how to compute the offset) ++ pj_uint16_t temp = size - CRC_SIZE; ++ pj_uint32_t crc = *(uint32_t*)(buffer + temp); ++ crc = pj_ntohl(crc); ++ ++ if (!zrtp_CheckCksum(buffer, temp, crc)) ++ { ++ if (zrtp->cb.show_message) ++ zrtp->cb.show_message(&zrtp->base, zrtp_Warning, zrtp_WarningCRCmismatch); ++ return; ++ } ++ ++ pj_uint32_t magic = *(pj_uint32_t*)(buffer + 4); ++ magic = pj_ntohl(magic); ++ ++ // Check if it is really a ZRTP packet, return, no further processing ++ if (magic != ZRTP_MAGIC) ++ return; ++ ++ // cover the case if the other party sends _only_ ZRTP packets at the ++ // beginning of a session. Start ZRTP in this case as well. ++ if (!zrtp->started) ++ { ++ pjmedia_transport_zrtp_startZrtp((pjmedia_transport *)zrtp); ++ } ++ // this now points beyond the undefined and length field. ++ // We need them, thus adjust ++ unsigned char* zrtpMsg = (buffer + 12); ++ ++ // store peer's SSRC in host order, used when creating the CryptoContext ++ zrtp->peerSSRC = *(pj_uint32_t*)(buffer + 8); ++ zrtp->peerSSRC = pj_ntohl(zrtp->peerSSRC); ++ zrtp_processZrtpMessage(zrtp->zrtpCtx, zrtpMsg, zrtp->peerSSRC, size); ++ } ++} ++ ++ ++/* This is our RTCP callback, that is called by the slave transport when it ++ * receives RTCP packet. ++ */ ++static void transport_rtcp_cb(void *user_data, void *pkt, pj_ssize_t size) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)user_data; ++ int32_t newLen = 0; ++ pj_status_t rc = PJ_SUCCESS; ++ ++ pj_assert(zrtp && zrtp->stream_rtcp_cb); ++ ++ if (zrtp->srtcpReceive == NULL || size < 0) ++ { ++ zrtp->stream_rtcp_cb(zrtp->stream_user_data, pkt, size); ++ } ++ else ++ { ++ rc = zsrtp_unprotectCtrl(zrtp->srtcpReceive, pkt, size, &newLen); ++ ++ if (rc == 1) ++ { ++ /* Call stream's callback */ ++ zrtp->stream_rtcp_cb(zrtp->stream_user_data, pkt, newLen); ++ } ++ else ++ { ++ // Testing: print some error output ++ } ++ } ++} ++ ++ ++/* ++ * attach() is called by stream to register callbacks that we should ++ * call on receipt of RTP and RTCP packets. ++ */ ++static pj_status_t transport_attach(pjmedia_transport *tp, ++ void *user_data, ++ const pj_sockaddr_t *rem_addr, ++ const pj_sockaddr_t *rem_rtcp, ++ unsigned addr_len, ++ void (*rtp_cb)(void*, ++ void*, ++ pj_ssize_t), ++ void (*rtcp_cb)(void*, ++ void*, ++ pj_ssize_t)) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; ++ pj_status_t status; ++ ++ PJ_ASSERT_RETURN(tp && rem_addr && addr_len, PJ_EINVAL); ++ ++ /* In this example, we will save the stream information and callbacks ++ * to our structure, and we will register different RTP/RTCP callbacks ++ * instead. ++ */ ++ pj_assert(zrtp->stream_user_data == NULL); ++ zrtp->stream_user_data = user_data; ++ zrtp->stream_rtp_cb = rtp_cb; ++ zrtp->stream_rtcp_cb = rtcp_cb; ++ ++ status = pjmedia_transport_attach(zrtp->slave_tp, zrtp, rem_addr, ++ rem_rtcp, addr_len, &transport_rtp_cb, ++ &transport_rtcp_cb); ++ if (status != PJ_SUCCESS) ++ { ++ zrtp->stream_user_data = NULL; ++ zrtp->stream_rtp_cb = NULL; ++ zrtp->stream_rtcp_cb = NULL; ++ return status; ++ } ++ ++ return PJ_SUCCESS; ++} ++ ++/* ++ * detach() is called when the media is terminated, and the stream is ++ * to be disconnected from us. ++ */ ++static void transport_detach(pjmedia_transport *tp, void *strm) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; ++ ++ PJ_UNUSED_ARG(strm); ++ PJ_ASSERT_ON_FAIL(tp, return); ++ ++ if (zrtp->stream_user_data != NULL) ++ { ++ pjmedia_transport_detach(zrtp->slave_tp, zrtp); ++ zrtp->stream_user_data = NULL; ++ zrtp->stream_rtp_cb = NULL; ++ zrtp->stream_rtcp_cb = NULL; ++ } ++} ++ ++ ++/* ++ * send_rtp() is called to send RTP packet. The "pkt" and "size" argument ++ * contain both the RTP header and the payload. ++ */ ++static pj_status_t transport_send_rtp(pjmedia_transport *tp, ++ const void *pkt, ++ pj_size_t size) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; ++ pj_uint32_t* pui = (pj_uint32_t*)pkt; ++ int32_t newLen = 0; ++ pj_status_t rc = PJ_SUCCESS; ++ ++ PJ_ASSERT_RETURN(tp && pkt, PJ_EINVAL); ++ ++ ++ if (!zrtp->started && zrtp->enableZrtp) ++ { ++ if (zrtp->localSSRC == 0) ++ zrtp->localSSRC = pj_ntohl(pui[2]); /* Learn own SSRC before starting ZRTP */ ++ ++ pjmedia_transport_zrtp_startZrtp((pjmedia_transport *)zrtp); ++ } ++ ++ if (zrtp->srtpSend == NULL) ++ { ++ return pjmedia_transport_send_rtp(zrtp->slave_tp, pkt, size); ++ } ++ else ++ { ++ if (size+80 > MAX_RTP_BUFFER_LEN) ++ return PJ_ETOOBIG; ++ ++ pj_memcpy(zrtp->sendBuffer, pkt, size); ++ rc = zsrtp_protect(zrtp->srtpSend, zrtp->sendBuffer, size, &newLen); ++ zrtp->protect++; ++ ++ if (rc == 1) ++ return pjmedia_transport_send_rtp(zrtp->slave_tp, zrtp->sendBuffer, newLen); ++ else ++ return PJ_EIGNORED; ++ } ++} ++ ++ ++/* ++ * send_rtcp() is called to send RTCP packet. The "pkt" and "size" argument ++ * contain the RTCP packet. ++ */ ++static pj_status_t transport_send_rtcp(pjmedia_transport *tp, ++ const void *pkt, ++ pj_size_t size) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; ++ pj_status_t rc = PJ_SUCCESS; ++ int32_t newLen = 0; ++ PJ_ASSERT_RETURN(tp, PJ_EINVAL); ++ ++ /* You may do some processing to the RTCP packet here if you want. */ ++ if (zrtp->srtcpSend == NULL) ++ { ++ return pjmedia_transport_send_rtcp(zrtp->slave_tp, pkt, size); ++ } ++ else ++ { ++ if (size+80 > MAX_RTCP_BUFFER_LEN) ++ return PJ_ETOOBIG; ++ ++ pj_memcpy(zrtp->sendBufferCtrl, pkt, size); ++ rc = zsrtp_protectCtrl(zrtp->srtcpSend, zrtp->sendBufferCtrl, size, &newLen); ++ ++ if (rc == 1) ++ return pjmedia_transport_send_rtcp(zrtp->slave_tp, zrtp->sendBufferCtrl, newLen); ++ else ++ return PJ_EIGNORED; ++ } ++ ++ /* Send the packet using the slave transport */ ++// return pjmedia_transport_send_rtcp(zrtp->slave_tp, pkt, size); ++} ++ ++ ++/* ++ * This is another variant of send_rtcp(), with the alternate destination ++ * address in the argument. ++ */ ++static pj_status_t transport_send_rtcp2(pjmedia_transport *tp, ++ const pj_sockaddr_t *addr, ++ unsigned addr_len, ++ const void *pkt, ++ pj_size_t size) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; ++ PJ_ASSERT_RETURN(tp, PJ_EINVAL); ++ ++ return pjmedia_transport_send_rtcp2(zrtp->slave_tp, addr, addr_len, ++ pkt, size); ++} ++ ++/* ++ * The media_create() is called when the transport is about to be used for ++ * a new call. ++ */ ++static pj_status_t transport_media_create(pjmedia_transport *tp, ++ pj_pool_t *sdp_pool, ++ unsigned options, ++ const pjmedia_sdp_session *rem_sdp, ++ unsigned media_index) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; ++ PJ_ASSERT_RETURN(tp, PJ_EINVAL); ++ ++ /* if "rem_sdp" is not NULL, it means we are UAS. You may do some ++ * inspections on the incoming SDP to verify that the SDP is acceptable ++ * for us. If the SDP is not acceptable, we can reject the SDP by ++ * returning non-PJ_SUCCESS. ++ */ ++ if (rem_sdp) ++ { ++ /* Do your stuff.. */ ++ } ++ ++ /* Once we're done with our initialization, pass the call to the ++ * slave transports to let it do it's own initialization too. ++ */ ++ return pjmedia_transport_media_create(zrtp->slave_tp, sdp_pool, options, ++ rem_sdp, media_index); ++} ++ ++/* ++ * The encode_sdp() is called when we're about to send SDP to remote party, ++ * either as SDP offer or as SDP answer. ++ */ ++static pj_status_t transport_encode_sdp(pjmedia_transport *tp, ++ pj_pool_t *sdp_pool, ++ pjmedia_sdp_session *local_sdp, ++ const pjmedia_sdp_session *rem_sdp, ++ unsigned media_index) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; ++ int32_t numVersions, i; ++ ++ PJ_ASSERT_RETURN(tp, PJ_EINVAL); ++ ++ /* If "rem_sdp" is not NULL, it means we're encoding SDP answer. You may ++ * do some more checking on the SDP's once again to make sure that ++ * everything is okay before we send SDP. ++ */ ++ if (rem_sdp) ++ { ++ /* Do checking stuffs here.. */ ++ } ++ ++ /* Add zrtp-hash attributes to both INVITE and 200 OK. */ ++ numVersions = zrtp_getNumberSupportedVersions(zrtp->zrtpCtx); ++ for (i = 0; i < numVersions; i++) { ++ char *zrtp_hello_hash = zrtp_getHelloHash(zrtp->zrtpCtx, i); ++ if (zrtp_hello_hash && *zrtp_hello_hash) { ++ int zrtp_hello_hash_len = strlen(zrtp_hello_hash); ++ pj_str_t *zrtp_hash_str = PJ_POOL_ALLOC_T(sdp_pool, pj_str_t); ++ pjmedia_sdp_attr *zrtp_hash = NULL; ++ ++ zrtp_hash_str->ptr = zrtp_hello_hash; ++ zrtp_hash_str->slen = zrtp_hello_hash_len; ++ ++ zrtp_hash = pjmedia_sdp_attr_create(sdp_pool, "zrtp-hash", zrtp_hash_str); ++ if (zrtp_hash && ++ pjmedia_sdp_attr_add(&local_sdp->media[media_index]->attr_count, local_sdp->media[media_index]->attr, zrtp_hash) == PJ_SUCCESS) { ++ PJ_LOG(4, (THIS_FILE, "attribute added: a=zrtp-hash:%s", zrtp_hello_hash)); ++ } ++ else { ++ PJ_LOG(4, (THIS_FILE, "error adding attribute: a=zrtp-hash:%s", zrtp_hello_hash)); ++ } ++ } ++ } ++ ++ /* You may do anything to the local_sdp, e.g. adding new attributes, or ++ * even modifying the SDP if you want. ++ */ ++ if (0) ++ { ++ /* Say we add a proprietary attribute here.. */ ++ pjmedia_sdp_attr *my_attr; ++ ++ my_attr = PJ_POOL_ALLOC_T(sdp_pool, pjmedia_sdp_attr); ++ pj_strdup2(sdp_pool, &my_attr->name, "X-zrtp"); ++ pj_strdup2(sdp_pool, &my_attr->value, "some value"); ++ ++ pjmedia_sdp_attr_add(&local_sdp->media[media_index]->attr_count, ++ local_sdp->media[media_index]->attr, ++ my_attr); ++ } ++ ++ /* And then pass the call to slave transport to let it encode its ++ * information in the SDP. You may choose to call encode_sdp() to slave ++ * first before adding your custom attributes if you want. ++ */ ++ return pjmedia_transport_encode_sdp(zrtp->slave_tp, sdp_pool, local_sdp, rem_sdp, media_index); ++} ++ ++/* ++ * The media_start() is called once both local and remote SDP have been ++ * negotiated successfully, and the media is ready to start. Here we can start ++ * committing our processing. ++ */ ++static pj_status_t transport_media_start(pjmedia_transport *tp, ++ pj_pool_t *pool, ++ const pjmedia_sdp_session *local_sdp, ++ const pjmedia_sdp_session *rem_sdp, ++ unsigned media_index) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; ++ PJ_ASSERT_RETURN(tp, PJ_EINVAL); ++ ++ /* Do something.. */ ++ ++ /* And pass the call to the slave transport */ ++ return pjmedia_transport_media_start(zrtp->slave_tp, pool, local_sdp, ++ rem_sdp, media_index); ++} ++ ++/* ++ * The media_stop() is called when media has been stopped. ++ */ ++static pj_status_t transport_media_stop(pjmedia_transport *tp) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; ++ PJ_ASSERT_RETURN(tp, PJ_EINVAL); ++ ++ /* Do something.. */ ++ PJ_LOG(4, (THIS_FILE, "Media stop - encrypted packets: %ld, decrypted packets: %ld", ++ zrtp->protect, zrtp->unprotect)); ++ ++ /* And pass the call to the slave transport */ ++ return pjmedia_transport_media_stop(zrtp->slave_tp); ++} ++ ++/* ++ * simulate_lost() is called to simulate packet lost ++ */ ++static pj_status_t transport_simulate_lost(pjmedia_transport *tp, ++ pjmedia_dir dir, ++ unsigned pct_lost) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; ++ ++ PJ_ASSERT_RETURN(tp, PJ_EINVAL); ++ ++ return pjmedia_transport_simulate_lost(zrtp->slave_tp, dir, pct_lost); ++} ++ ++/* ++ * destroy() is called when the transport is no longer needed. ++ */ ++static pj_status_t transport_destroy(pjmedia_transport *tp) ++{ ++ struct tp_zrtp *zrtp = (struct tp_zrtp*)tp; ++ ++ PJ_ASSERT_RETURN(tp, PJ_EINVAL); ++ ++ PJ_LOG(4, (THIS_FILE, "Destroy - encrypted packets: %ld, decrypted packets: %ld", ++ zrtp->protect, zrtp->unprotect)); ++ ++ /* close the slave transport in case */ ++ if (zrtp->close_slave && zrtp->slave_tp) ++ pjmedia_transport_close(zrtp->slave_tp); ++ ++ /* Self destruct.. */ ++ zrtp_stopZrtpEngine(zrtp->zrtpCtx); ++ zrtp_DestroyWrapper(zrtp->zrtpCtx); ++ zrtp->zrtpCtx = NULL; ++ ++ /* In case mutex is being acquired by other thread */ ++ pj_mutex_lock(zrtp->zrtpMutex); ++ pj_mutex_unlock(zrtp->zrtpMutex); ++ pj_mutex_destroy(zrtp->zrtpMutex); ++ ++ pj_pool_release(zrtp->pool); ++ ++ return PJ_SUCCESS; ++} ++ ++ ++ ++ +diff -ruN pjproject-2.10/pjmedia/src/pjmedia/vid_stream.c pjsip/pjmedia/src/pjmedia/vid_stream.c +--- pjproject-2.10/pjmedia/src/pjmedia/vid_stream.c 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/pjmedia/src/pjmedia/vid_stream.c 2021-02-06 21:17:10.577365678 +0100 +@@ -139,9 +139,15 @@ + unsigned dec_delay_cnt; /**< Decoding delay (in frames).*/ + pjmedia_event fmt_event; /**< Buffered fmt_changed event + to avoid deadlock */ ++ pjmedia_event found_keyframe_event; ++ /**< Buffered found keyframe ++ event for delayed republish*/ + pjmedia_event miss_keyframe_event; + /**< Buffered missing keyframe + event for delayed republish*/ ++ pjmedia_event keyframe_req_event; ++ /**< Buffered keyframe request ++ event for delayed republish*/ + + unsigned frame_size; /**< Size of encoded base frame.*/ + unsigned frame_ts_len; /**< Frame length in timestamp. */ +@@ -410,6 +416,11 @@ + pj_memcpy(&stream->fmt_event, event, sizeof(*event)); + return PJ_SUCCESS; + ++ case PJMEDIA_EVENT_KEYFRAME_FOUND: ++ /* Republish this event later from get_frame(). */ ++ pj_memcpy(&stream->found_keyframe_event, event, sizeof(*event)); ++ return PJ_SUCCESS; ++ + case PJMEDIA_EVENT_KEYFRAME_MISSING: + /* Republish this event later from get_frame(). */ + pj_memcpy(&stream->miss_keyframe_event, event, sizeof(*event)); +@@ -1028,6 +1039,14 @@ + } + + pjmedia_rtcp_rx_rtcp(&stream->rtcp, pkt, bytes_read); ++ ++ /* XXX: posting some event from the RTCP session might be a better option */ ++ if (stream->rtcp.keyframe_requested) { ++ pjmedia_event event; ++ pjmedia_event_init(&event, PJMEDIA_EVENT_KEYFRAME_REQUESTED, NULL, stream); ++ pj_memcpy(&stream->keyframe_req_event, &event, sizeof(event)); ++ } ++ + } + + static pj_status_t put_frame(pjmedia_port *port, +@@ -1419,8 +1438,8 @@ + new_fps.denum = ts_diff; + } + +- /* Only apply the new FPS when it is >0, <=100, and increasing */ +- if (new_fps.num/new_fps.denum <= 100 && ++ /* Only apply the new FPS when it is >0, <=60, and increasing */ ++ if (new_fps.num/new_fps.denum <= 60 && + new_fps.num/new_fps.denum > 0 && + new_fps.num*1.0/new_fps.denum > + stream->dec_max_fps.num*1.0/stream->dec_max_fps.denum) +@@ -1516,8 +1535,10 @@ + /* Override the framerate to be 1.5x higher in the event + * for the renderer. + */ ++#if 0 + fmt_chg_data->new_fmt.det.vid.fps.num *= 3; + fmt_chg_data->new_fmt.det.vid.fps.num /= 2; ++#endif + } else { + pjmedia_format_copy(&stream->info.codec_param->enc_fmt, + &fmt_chg_data->new_fmt); +@@ -1534,12 +1555,25 @@ + stream->fmt_event.type = PJMEDIA_EVENT_NONE; + } + ++ if (stream->found_keyframe_event.type != PJMEDIA_EVENT_NONE) { ++ pjmedia_event_publish(NULL, port, &stream->found_keyframe_event, ++ PJMEDIA_EVENT_PUBLISH_POST_EVENT); ++ stream->found_keyframe_event.type = PJMEDIA_EVENT_NONE; ++ } ++ + if (stream->miss_keyframe_event.type != PJMEDIA_EVENT_NONE) { + pjmedia_event_publish(NULL, port, &stream->miss_keyframe_event, + PJMEDIA_EVENT_PUBLISH_POST_EVENT); + stream->miss_keyframe_event.type = PJMEDIA_EVENT_NONE; + } + ++ if (stream->keyframe_req_event.type != PJMEDIA_EVENT_NONE) { ++ pjmedia_event_publish(NULL, port, &stream->keyframe_req_event, ++ PJMEDIA_EVENT_PUBLISH_POST_EVENT); ++ stream->keyframe_req_event.type = PJMEDIA_EVENT_NONE; ++ } ++ ++ + pj_mutex_lock( stream->jb_mutex ); + + if (stream->dec_frame.size == 0) { +@@ -1846,7 +1880,9 @@ + * local renderer clock) or video setup lag. Note that the actual framerate + * will be continuously calculated based on the incoming RTP timestamps. + */ ++#if 0 + vfd_dec->fps.num = vfd_dec->fps.num * 3 / 2; ++#endif + stream->dec_max_fps = vfd_dec->fps; + + /* Create decoder channel */ +@@ -2362,6 +2398,46 @@ + + return PJ_SUCCESS; + } ++ ++/* ++ * Send RTCP PLI. ++ */ ++PJ_DEF(pj_status_t) pjmedia_vid_stream_send_rtcp_pli( ++ pjmedia_vid_stream *stream) ++{ ++ PJ_ASSERT_RETURN(stream, PJ_EINVAL); ++ ++ if (stream->enc && stream->transport) { ++ void *sr_rr_pkt; ++ pj_uint8_t *pkt; ++ int len, max_len; ++ pj_status_t status; ++ pj_size_t pli_len; ++ ++ /* Build RTCP RR/SR packet */ ++ pjmedia_rtcp_build_rtcp(&stream->rtcp, &sr_rr_pkt, &len); ++ ++ pkt = (pj_uint8_t*) stream->out_rtcp_pkt; ++ pj_memcpy(pkt, sr_rr_pkt, len); ++ max_len = stream->out_rtcp_pkt_size; ++ ++ /* Build RTCP PLI packet */ ++ pli_len = max_len - len; ++ status = pjmedia_rtcp_build_rtcp_pli(&stream->rtcp, pkt+len, &pli_len); ++ if (status != PJ_SUCCESS) { ++ PJ_PERROR(4,(stream->name.ptr, status, "Error generating RTCP PLI")); ++ } else { ++ len += (int)pli_len; ++ } ++ ++ /* Send! */ ++ status = pjmedia_transport_send_rtcp(stream->transport, pkt, len); ++ ++ return status; ++ } ++ ++ return PJ_SUCCESS; ++} + + + /* +diff -ruN pjproject-2.10/pjmedia/src/pjmedia/vid_tee.c pjsip/pjmedia/src/pjmedia/vid_tee.c +--- pjproject-2.10/pjmedia/src/pjmedia/vid_tee.c 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/pjmedia/src/pjmedia/vid_tee.c 2021-02-06 21:28:30.516675163 +0100 +@@ -52,6 +52,7 @@ + unsigned dst_port_cnt; + vid_tee_dst_port *dst_ports; + pj_uint8_t *put_frm_flag; ++ pj_mutex_t *lock; + + struct vid_tee_conv_t { + pjmedia_converter *conv; +@@ -86,6 +87,11 @@ + tee->pf = pool->factory; + tee->pool = pj_pool_create(tee->pf, "video tee", 500, 500, NULL); + ++ /* Create lock */ ++ status = pj_mutex_create_simple(pool, "vid-tee-mutex", &tee->lock); ++ if (status != PJ_SUCCESS) ++ return status; ++ + /* Initialize video tee structure */ + tee->dst_port_maxcnt = max_dst_cnt; + tee->dst_ports = (vid_tee_dst_port*) +@@ -100,14 +106,16 @@ + + /* Initialize video tee buffer, its size is one frame */ + vfi = pjmedia_get_video_format_info(NULL, fmt->id); +- if (vfi == NULL) +- return PJMEDIA_EBADFMT; ++ if (vfi == NULL) { ++ status = PJMEDIA_EBADFMT; ++ goto on_error; ++ } + + pj_bzero(&vafp, sizeof(vafp)); + vafp.size = fmt->det.vid.size; + status = vfi->apply_fmt(vfi, &vafp); + if (status != PJ_SUCCESS) +- return status; ++ goto on_error; + + tee->buf_size = vafp.framebytes; + +@@ -118,7 +126,7 @@ + PJMEDIA_DIR_ENCODING, + fmt); + if (status != PJ_SUCCESS) +- return status; ++ goto on_error;; + + tee->base.get_frame = &tee_get_frame; + tee->base.put_frame = &tee_put_frame; +@@ -128,6 +136,12 @@ + *p_vid_tee = &tee->base; + + return PJ_SUCCESS; ++ ++on_error: ++ pj_mutex_destroy(tee->lock); ++ tee->lock = NULL; ++ return status; ++ + } + + static void realloc_buf(vid_tee_port *vid_tee, +@@ -169,21 +183,29 @@ + { + vid_tee_port *tee = (vid_tee_port*)vid_tee; + pjmedia_video_format_detail *vfd; ++ pj_status_t status; + + PJ_ASSERT_RETURN(vid_tee && vid_tee->info.signature==TEE_PORT_SIGN, + PJ_EINVAL); + +- if (tee->dst_port_cnt >= tee->dst_port_maxcnt) +- return PJ_ETOOMANY; +- +- if (vid_tee->info.fmt.id != port->info.fmt.id) +- return PJMEDIA_EBADFMT; ++ pj_mutex_lock(tee->lock); ++ ++ if (tee->dst_port_cnt >= tee->dst_port_maxcnt) { ++ status = PJ_ETOOMANY; ++ goto end; ++ } ++ ++ if (vid_tee->info.fmt.id != port->info.fmt.id) { ++ status = PJMEDIA_EBADFMT; ++ goto end; ++ } + + vfd = pjmedia_format_get_video_format_detail(&port->info.fmt, PJ_TRUE); + if (vfd->size.w != vid_tee->info.fmt.det.vid.size.w || + vfd->size.h != vid_tee->info.fmt.det.vid.size.h) + { +- return PJMEDIA_EBADFMT; ++ status = PJMEDIA_EBADFMT; ++ goto end; + } + + realloc_buf(tee, (option & PJMEDIA_VID_TEE_DST_DO_IN_PLACE_PROC)? +@@ -194,7 +216,12 @@ + tee->dst_ports[tee->dst_port_cnt].option = option; + ++tee->dst_port_cnt; + +- return PJ_SUCCESS; ++ status = PJ_SUCCESS; ++ ++end: ++ pj_mutex_unlock(tee->lock); ++ return status; ++ + } + + +@@ -208,12 +235,17 @@ + { + vid_tee_port *tee = (vid_tee_port*)vid_tee; + pjmedia_video_format_detail *vfd; ++ pj_status_t status; + + PJ_ASSERT_RETURN(vid_tee && vid_tee->info.signature==TEE_PORT_SIGN, + PJ_EINVAL); +- +- if (tee->dst_port_cnt >= tee->dst_port_maxcnt) +- return PJ_ETOOMANY; ++ ++ pj_mutex_lock(tee->lock); ++ ++ if (tee->dst_port_cnt >= tee->dst_port_maxcnt) { ++ status = PJ_ETOOMANY; ++ goto end; ++ } + + pj_bzero(&tee->tee_conv[tee->dst_port_cnt], sizeof(tee->tee_conv[0])); + +@@ -226,17 +258,18 @@ + const pjmedia_video_format_info *vfi; + pjmedia_video_apply_fmt_param vafp; + pjmedia_conversion_param conv_param; +- pj_status_t status; + + vfi = pjmedia_get_video_format_info(NULL, port->info.fmt.id); + if (vfi == NULL) +- return PJMEDIA_EBADFMT; ++ status = PJMEDIA_EBADFMT; ++ goto end; ++ } + + pj_bzero(&vafp, sizeof(vafp)); + vafp.size = port->info.fmt.det.vid.size; + status = vfi->apply_fmt(vfi, &vafp); + if (status != PJ_SUCCESS) +- return status; ++ goto end; + + realloc_buf(tee, (option & PJMEDIA_VID_TEE_DST_DO_IN_PLACE_PROC)? + 2: 1, vafp.framebytes); +@@ -248,7 +281,7 @@ + NULL, tee->pool, &conv_param, + &tee->tee_conv[tee->dst_port_cnt].conv); + if (status != PJ_SUCCESS) +- return status; ++ goto end; + + tee->tee_conv[tee->dst_port_cnt].conv_buf_size = vafp.framebytes; + } else { +@@ -259,8 +292,12 @@ + tee->dst_ports[tee->dst_port_cnt].dst = port; + tee->dst_ports[tee->dst_port_cnt].option = option; + ++tee->dst_port_cnt; +- +- return PJ_SUCCESS; ++ ++ status = PJ_SUCCESS; ++ ++end: ++ pj_mutex_unlock(tee->lock); ++ return status; + } + + +@@ -276,6 +313,8 @@ + PJ_ASSERT_RETURN(vid_tee && vid_tee->info.signature==TEE_PORT_SIGN, + PJ_EINVAL); + ++ pj_mutex_lock(tee->lock); ++ + for (i = 0; i < tee->dst_port_cnt; ++i) { + if (tee->dst_ports[i].dst == port) { + if (tee->tee_conv[i].conv) +@@ -286,10 +325,13 @@ + pj_array_erase(tee->tee_conv, sizeof(tee->tee_conv[0]), + tee->dst_port_cnt, i); + --tee->dst_port_cnt; ++ ++ pj_mutex_unlock(tee->lock); + return PJ_SUCCESS; + } + } + ++ pj_mutex_unlock(tee->lock); + return PJ_ENOTFOUND; + } + +@@ -300,6 +342,12 @@ + unsigned i, j; + const pj_uint8_t PUT_FRM_DONE = 1; + ++ if (pj_mutex_trylock(tee->lock) != PJ_SUCCESS) { ++ /* we are busy adding / removing consumers */ ++ return PJ_SUCCESS; ++ } ++ ++ + pj_bzero(tee->put_frm_flag, tee->dst_port_cnt * + sizeof(tee->put_frm_flag[0])); + +@@ -364,6 +412,7 @@ + } + } + ++ pj_mutex_unlock(tee->lock); + return PJ_SUCCESS; + } + +@@ -383,6 +432,11 @@ + + PJ_ASSERT_RETURN(port && port->info.signature==TEE_PORT_SIGN, PJ_EINVAL); + ++ if (tee->lock) { ++ pj_mutex_destroy(tee->lock); ++ tee->lock = NULL; ++ } ++ + pj_pool_release(tee->pool); + if (tee->buf_pool) + pj_pool_release(tee->buf_pool); +diff -ruN pjproject-2.10/pjmedia/src/pjmedia-audiodev/alsa_dev.c pjsip/pjmedia/src/pjmedia-audiodev/alsa_dev.c +--- pjproject-2.10/pjmedia/src/pjmedia-audiodev/alsa_dev.c 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/pjmedia/src/pjmedia-audiodev/alsa_dev.c 2021-02-06 23:08:42.203153000 +0100 +@@ -43,7 +43,7 @@ + #define ALSASOUND_CAPTURE 2 + #define MAX_SOUND_CARDS 5 + #define MAX_SOUND_DEVICES_PER_CARD 5 +-#define MAX_DEVICES 32 ++#define MAX_DEVICES 128 + #define MAX_MIX_NAME_LEN 64 + + /* Set to 1 to enable tracing */ +@@ -74,6 +74,10 @@ + pjmedia_aud_play_cb play_cb, + void *user_data, + pjmedia_aud_stream **p_strm); ++static void alsa_factory_set_observer(pjmedia_aud_dev_factory *f, ++ pjmedia_aud_dev_change_callback cb); ++static int alsa_factory_get_default_rec_dev(pjmedia_aud_dev_factory *f); ++static int alsa_factory_get_default_play_dev(pjmedia_aud_dev_factory *f); + + /* + * Stream prototypes +@@ -90,6 +94,15 @@ + static pj_status_t alsa_stream_stop(pjmedia_aud_stream *strm); + static pj_status_t alsa_stream_destroy(pjmedia_aud_stream *strm); + ++/* alsa device info */ ++struct alsa_dev_info ++{ ++ pjmedia_aud_dev_info info; ++ char alsa_name[64]; ++ int input_count; ++ int output_count; ++}; ++ + + struct alsa_factory + { +@@ -99,7 +112,7 @@ + pj_pool_t *base_pool; + + unsigned dev_cnt; +- pjmedia_aud_dev_info devs[MAX_DEVICES]; ++ struct alsa_dev_info devs[MAX_DEVICES]; + char pb_mixer_name[MAX_MIX_NAME_LEN]; + }; + +@@ -140,7 +153,10 @@ + &alsa_factory_get_dev_info, + &alsa_factory_default_param, + &alsa_factory_create_stream, +- &alsa_factory_refresh ++ &alsa_factory_refresh, ++ &alsa_factory_set_observer, ++ &alsa_factory_get_default_rec_dev, ++ &alsa_factory_get_default_play_dev + }; + + static pjmedia_aud_stream_op alsa_stream_op = +@@ -214,9 +230,9 @@ + } + + +-static pj_status_t add_dev (struct alsa_factory *af, const char *dev_name) ++static pj_status_t add_dev (struct alsa_factory *af, const char *dev_name, const char *dev_desc) + { +- pjmedia_aud_dev_info *adi; ++ struct alsa_dev_info *adi; + snd_pcm_t* pcm; + int pb_result, ca_result; + +@@ -258,23 +274,63 @@ + pj_bzero(adi, sizeof(*adi)); + + /* Set device name */ +- strncpy(adi->name, dev_name, sizeof(adi->name)); ++ strncpy(adi->alsa_name, dev_name, sizeof(adi->alsa_name)); ++ ++ /* Set comprehensive device name */ ++ int name_size = sizeof(adi->info.name); ++ if (dev_desc) { ++ pj_bool_t name_set = PJ_FALSE; ++ if (strncmp("sysdefault", dev_name, 10) == 0) { ++ /* Only use first line for default device*/ ++ char *ptr = strstr(dev_desc, "\n"); ++ if (ptr) { ++ int len = ptr - dev_desc; ++ strncpy(adi->info.name, dev_desc, (len >= name_size-1)?name_size:len); ++ name_set = PJ_TRUE; ++ } ++ } else if (strncmp("iec958", dev_name, 6) == 0) { ++ /* Mangle name for SPDIF devices*/ ++ char *ptr = strstr(dev_desc, ","); ++ if (ptr) { ++ int len = ptr - dev_desc; ++ if (len + 18 < name_size) { ++ strncpy(adi->info.name, dev_desc, len); ++ strncpy(adi->info.name+len, ", Digital (S/PDIF)", 18); ++ name_set = PJ_TRUE; ++ } ++ } ++ } ++ ++ if (!name_set) { ++ /* Use the entire description for other device names */ ++ int i = 0; ++ while (i < name_size-1 && dev_desc[i] != '\0') { ++ if (dev_desc[i] == '\n' || dev_desc[i] == '\r') ++ adi->info.name[i] = ' '; ++ else ++ adi->info.name[i] = dev_desc[i]; ++ i++; ++ } ++ } ++ } else { ++ strncpy(adi->info.name, dev_name, name_size); ++ } + + /* Check the number of playback channels */ +- adi->output_count = (pb_result>=0) ? 1 : 0; ++ adi->info.output_count = (pb_result>=0) ? 1 : 0; + + /* Check the number of capture channels */ +- adi->input_count = (ca_result>=0) ? 1 : 0; ++ adi->info.input_count = (ca_result>=0) ? 1 : 0; + + /* Set the default sample rate */ +- adi->default_samples_per_sec = 8000; ++ adi->info.default_samples_per_sec = 8000; + + /* Driver name */ +- strcpy(adi->driver, "ALSA"); ++ strcpy(adi->info.driver, "ALSA"); + + ++af->dev_cnt; + +- PJ_LOG (5,(THIS_FILE, "Added sound device %s", adi->name)); ++ PJ_LOG (5,(THIS_FILE, "Added sound device %s", adi->alsa_name)); + + return PJ_SUCCESS; + } +@@ -399,10 +455,26 @@ + n = hints; + while (*n != NULL) { + char *name = snd_device_name_get_hint(*n, "NAME"); +- if (name != NULL) { +- if (0 != strcmp("null", name)) +- add_dev(af, name); ++ char *desc = snd_device_name_get_hint(*n, "DESC"); ++ if (name != NULL) { ++ if (strncmp("null", name, 4) == 0 || ++ strncmp("front", name, 5) == 0 || ++ strncmp("rear", name, 4) == 0 || ++ strncmp("side", name, 4) == 0 || ++ strncmp("dmix", name, 4) == 0 || ++ strncmp("dsnoop", name, 6) == 0 || ++ strncmp("hw", name, 2) == 0 || ++ strncmp("plughw", name, 6) == 0 || ++ strncmp("center_lfe", name, 10) == 0 || ++ strncmp("surround", name, 8) == 0 || ++ (strncmp("default", name, 7) == 0 && strstr(name, ":CARD=") != NULL)) { ++ /* skip these devices, 'sysdefault' always contains the relevant information */ ++ ; ++ } else { ++ add_dev(af, name, desc); ++ } + free(name); ++ free(desc); + } + n++; + } +@@ -440,7 +512,7 @@ + + PJ_ASSERT_RETURN(index>=0 && indexdev_cnt, PJ_EINVAL); + +- pj_memcpy(info, &af->devs[index], sizeof(*info)); ++ pj_memcpy(info, &af->devs[index].info, sizeof(*info)); + info->caps = PJMEDIA_AUD_DEV_CAP_INPUT_LATENCY | + PJMEDIA_AUD_DEV_CAP_OUTPUT_LATENCY; + return PJ_SUCCESS; +@@ -452,22 +524,22 @@ + pjmedia_aud_param *param) + { + struct alsa_factory *af = (struct alsa_factory*)f; +- pjmedia_aud_dev_info *adi; ++ struct alsa_dev_info *adi; + + PJ_ASSERT_RETURN(index>=0 && indexdev_cnt, PJ_EINVAL); + + adi = &af->devs[index]; + + pj_bzero(param, sizeof(*param)); +- if (adi->input_count && adi->output_count) { ++ if (adi->info.input_count && adi->info.output_count) { + param->dir = PJMEDIA_DIR_CAPTURE_PLAYBACK; + param->rec_id = index; + param->play_id = index; +- } else if (adi->input_count) { ++ } else if (adi->info.input_count) { + param->dir = PJMEDIA_DIR_CAPTURE; + param->rec_id = index; + param->play_id = PJMEDIA_AUD_INVALID_DEV; +- } else if (adi->output_count) { ++ } else if (adi->info.output_count) { + param->dir = PJMEDIA_DIR_PLAYBACK; + param->play_id = index; + param->rec_id = PJMEDIA_AUD_INVALID_DEV; +@@ -475,11 +547,11 @@ + return PJMEDIA_EAUD_INVDEV; + } + +- param->clock_rate = adi->default_samples_per_sec; ++ param->clock_rate = adi->info.default_samples_per_sec; + param->channel_count = 1; +- param->samples_per_frame = adi->default_samples_per_sec * 20 / 1000; ++ param->samples_per_frame = adi->info.default_samples_per_sec * 20 / 1000; + param->bits_per_sample = 16; +- param->flags = adi->caps; ++ param->flags = adi->info.caps; + param->input_latency_ms = PJMEDIA_SND_DEFAULT_REC_LATENCY; + param->output_latency_ms = PJMEDIA_SND_DEFAULT_PLAY_LATENCY; + +@@ -626,9 +698,9 @@ + + /* Open PCM for playback */ + PJ_LOG (5,(THIS_FILE, "open_playback: Open playback device '%s'", +- stream->af->devs[param->play_id].name)); ++ stream->af->devs[param->play_id].alsa_name)); + result = snd_pcm_open (&stream->pb_pcm, +- stream->af->devs[param->play_id].name, ++ stream->af->devs[param->play_id].alsa_name, + SND_PCM_STREAM_PLAYBACK, + 0); + if (result < 0) +@@ -722,7 +794,7 @@ + + PJ_LOG (5,(THIS_FILE, "Opened device alsa(%s) for playing, sample rate=%d" + ", ch=%d, bits=%d, period size=%d frames, latency=%d ms", +- stream->af->devs[param->play_id].name, ++ stream->af->devs[param->play_id].alsa_name, + rate, param->channel_count, + param->bits_per_sample, stream->pb_frames, + (int)stream->param.output_latency_ms)); +@@ -746,9 +818,9 @@ + + /* Open PCM for capture */ + PJ_LOG (5,(THIS_FILE, "open_capture: Open capture device '%s'", +- stream->af->devs[param->rec_id].name)); ++ stream->af->devs[param->rec_id].alsa_name)); + result = snd_pcm_open (&stream->ca_pcm, +- stream->af->devs[param->rec_id].name, ++ stream->af->devs[param->rec_id].alsa_name, + SND_PCM_STREAM_CAPTURE, + 0); + if (result < 0) +@@ -842,7 +914,7 @@ + + PJ_LOG (5,(THIS_FILE, "Opened device alsa(%s) for capture, sample rate=%d" + ", ch=%d, bits=%d, period size=%d frames, latency=%d ms", +- stream->af->devs[param->rec_id].name, ++ stream->af->devs[param->rec_id].alsa_name, + rate, param->channel_count, + param->bits_per_sample, stream->ca_frames, + (int)stream->param.input_latency_ms)); +@@ -903,6 +975,27 @@ + return PJ_SUCCESS; + } + ++/* API: set audio device change observer */ ++static void alsa_factory_set_observer(pjmedia_aud_dev_factory *f, ++ pjmedia_aud_dev_change_callback cb) ++{ ++ PJ_UNUSED_ARG(f); ++ PJ_UNUSED_ARG(cb); ++} ++ ++/* API: get default recording device */ ++static int alsa_factory_get_default_rec_dev(pjmedia_aud_dev_factory *f) ++{ ++ PJ_UNUSED_ARG(f); ++ return -1; ++} ++ ++/* API: get default playback device */ ++static int alsa_factory_get_default_play_dev(pjmedia_aud_dev_factory *f) ++{ ++ PJ_UNUSED_ARG(f); ++ return -1; ++} + + /* API: get running parameter */ + static pj_status_t alsa_stream_get_param(pjmedia_aud_stream *s, +diff -ruN pjproject-2.10/pjmedia/src/pjmedia-audiodev/coreaudio_dev.m pjsip/pjmedia/src/pjmedia-audiodev/coreaudio_dev.m +--- pjproject-2.10/pjmedia/src/pjmedia-audiodev/coreaudio_dev.m 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/pjmedia/src/pjmedia-audiodev/coreaudio_dev.m 2021-02-06 22:51:16.641714862 +0100 +@@ -173,6 +173,11 @@ + void *user_data, + pjmedia_aud_stream **p_aud_strm); + ++static void ca_factory_set_observer(pjmedia_aud_dev_factory *f, ++ pjmedia_aud_dev_change_callback cb); ++static int ca_factory_get_default_rec_dev(pjmedia_aud_dev_factory *f); ++static int ca_factory_get_default_play_dev(pjmedia_aud_dev_factory *f); ++ + static pj_status_t ca_stream_get_param(pjmedia_aud_stream *strm, + pjmedia_aud_param *param); + static pj_status_t ca_stream_get_cap(pjmedia_aud_stream *strm, +@@ -206,7 +211,10 @@ + &ca_factory_get_dev_info, + &ca_factory_default_param, + &ca_factory_create_stream, +- &ca_factory_refresh ++ &ca_factory_refresh, ++ &ca_factory_set_observer, ++ &ca_factory_get_default_rec_dev, ++ &ca_factory_get_default_play_dev + }; + + static pjmedia_aud_stream_op stream_op = +@@ -717,6 +725,169 @@ + return PJ_SUCCESS; + } + ++static OSStatus property_listener_proc(AudioObjectID objectID, ++ UInt32 numberAddresses, ++ const AudioObjectPropertyAddress inAddresses[], ++ void *clientData) ++{ ++ pjmedia_aud_dev_change_callback cb = (pjmedia_aud_dev_change_callback)clientData; ++ pjmedia_aud_dev_change_event event; ++ UInt32 i; ++ ++ for(i = 0; i < numberAddresses; i++) { ++ event = 0; ++ switch (inAddresses[i].mSelector) { ++ case kAudioHardwarePropertyDefaultInputDevice: ++ event = DEFAULT_INPUT_CHANGED; ++ break; ++ case kAudioHardwarePropertyDefaultOutputDevice: ++ event = DEFAULT_OUTPUT_CHANGED; ++ break; ++ case kAudioHardwarePropertyDevices: ++ event = DEVICE_LIST_CHANGED; ++ break; ++ default: ++ break; ++ } ++ if (event > 0) { ++ (cb)(event); ++ } ++ } ++ ++ return noErr; ++} ++ ++/* API: set audio device change observer */ ++static void ca_factory_set_observer(pjmedia_aud_dev_factory *f, ++ pjmedia_aud_dev_change_callback cb) ++{ ++ AudioObjectPropertyAddress addr; ++ OSStatus ostatus; ++ ++ /* observer for devices list */ ++ addr.mSelector = kAudioHardwarePropertyDevices; ++ addr.mScope = kAudioObjectPropertyScopeGlobal; ++ addr.mElement = kAudioObjectPropertyElementMaster; ++ ++ if (cb) { ++ ostatus = AudioObjectAddPropertyListener(kAudioObjectSystemObject, ++ &addr, ++ property_listener_proc, ++ cb); ++ } else { ++ ostatus = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, ++ &addr, ++ property_listener_proc, ++ cb); ++ } ++ if (ostatus != noErr) { ++ PJ_LOG(5,(THIS_FILE, "Error %sregistering devices list observer", cb==NULL ? "un-" : "")); ++ } ++ ++ /* observer for default input device */ ++ addr.mSelector = kAudioHardwarePropertyDefaultInputDevice; ++ ++ if (cb) { ++ ostatus = AudioObjectAddPropertyListener(kAudioObjectSystemObject, ++ &addr, ++ property_listener_proc, ++ cb); ++ } else { ++ ostatus = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, ++ &addr, ++ property_listener_proc, ++ cb); ++ } ++ if (ostatus != noErr) { ++ PJ_LOG(5,(THIS_FILE, "Error %sregistering default input device observer", cb==NULL ? "un-" : "")); ++ } ++ ++ /* observer for default output device */ ++ addr.mSelector = kAudioHardwarePropertyDefaultOutputDevice; ++ ++ if (cb) { ++ ostatus = AudioObjectAddPropertyListener(kAudioObjectSystemObject, ++ &addr, ++ property_listener_proc, ++ cb); ++ } else { ++ ostatus = AudioObjectRemovePropertyListener(kAudioObjectSystemObject, ++ &addr, ++ property_listener_proc, ++ cb); ++ } ++ if (ostatus != noErr) { ++ PJ_LOG(5,(THIS_FILE, "Error %sregistering default output device observer", cb==NULL ? "un-" : "")); ++ } ++ ++} ++ ++/* API: get default recording device */ ++static int ca_factory_get_default_rec_dev(pjmedia_aud_dev_factory *f) ++{ ++ AudioDeviceID dev_id = kAudioObjectUnknown; ++ AudioObjectPropertyAddress addr; ++ UInt32 size; ++ OSStatus ostatus; ++ int i; ++ int idx = -1; ++ struct coreaudio_factory *cf = (struct coreaudio_factory*)f; ++ ++ /* Find default audio input device */ ++ addr.mSelector = kAudioHardwarePropertyDefaultInputDevice; ++ addr.mScope = kAudioObjectPropertyScopeGlobal; ++ addr.mElement = kAudioObjectPropertyElementMaster; ++ size = sizeof(dev_id); ++ ++ ostatus = AudioObjectGetPropertyData(kAudioObjectSystemObject, ++ &addr, 0, NULL, ++ &size, (void *)&dev_id); ++ if (ostatus == noErr) { ++ for (i = 0; i < cf->dev_count; i++) { ++ struct coreaudio_dev_info *cdi; ++ cdi = &cf->dev_info[i]; ++ if (cdi->dev_id == dev_id) { ++ idx = i; ++ break; ++ } ++ } ++ } ++ return idx; ++} ++ ++/* API: get default playback device */ ++static int ca_factory_get_default_play_dev(pjmedia_aud_dev_factory *f) ++{ ++ AudioDeviceID dev_id = kAudioObjectUnknown; ++ AudioObjectPropertyAddress addr; ++ UInt32 size; ++ OSStatus ostatus; ++ int i; ++ int idx = -1; ++ struct coreaudio_factory *cf = (struct coreaudio_factory*)f; ++ ++ /* Find default audio output device */ ++ addr.mSelector = kAudioHardwarePropertyDefaultOutputDevice; ++ addr.mScope = kAudioObjectPropertyScopeGlobal; ++ addr.mElement = kAudioObjectPropertyElementMaster; ++ size = sizeof(dev_id); ++ ++ ostatus = AudioObjectGetPropertyData(kAudioObjectSystemObject, ++ &addr, 0, NULL, ++ &size, (void *)&dev_id); ++ if (ostatus == noErr) { ++ for (i = 0; i < cf->dev_count; i++) { ++ struct coreaudio_dev_info *cdi; ++ cdi = &cf->dev_info[i]; ++ if (cdi->dev_id == dev_id) { ++ idx = i; ++ break; ++ } ++ } ++ } ++ return idx; ++} ++ + OSStatus resampleProc(AudioConverterRef inAudioConverter, + UInt32 *ioNumberDataPackets, + AudioBufferList *ioData, +@@ -1862,7 +2033,6 @@ + { + strm->param.input_latency_ms = (latency + latency2) * 1000 / + strm->param.clock_rate; +- strm->param.input_latency_ms++; + } + } + #else +@@ -1870,7 +2040,6 @@ + strm->param.input_latency_ms = + (unsigned)(([strm->sess inputLatency] + + [strm->sess IOBufferDuration]) * 1000); +- strm->param.input_latency_ms++; + } else + return PJMEDIA_EAUD_INVCAP; + #endif +@@ -1903,7 +2072,6 @@ + { + strm->param.output_latency_ms = (latency + latency2) * 1000 / + strm->param.clock_rate; +- strm->param.output_latency_ms++; + } + } + #else +@@ -1911,11 +2079,10 @@ + strm->param.output_latency_ms = + (unsigned)(([strm->sess outputLatency] + + [strm->sess IOBufferDuration]) * 1000); +- strm->param.output_latency_ms++; + } else + return PJMEDIA_EAUD_INVCAP; + #endif +- *(unsigned*)pval = (++strm->param.output_latency_ms * 2); ++ *(unsigned*)pval = strm->param.output_latency_ms; + return PJ_SUCCESS; + } else if (cap==PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING && + (strm->param.dir & PJMEDIA_DIR_PLAYBACK)) +diff -ruN pjproject-2.10/pjmedia/src/pjmedia-audiodev/wmme_dev.c pjsip/pjmedia/src/pjmedia-audiodev/wmme_dev.c +--- pjproject-2.10/pjmedia/src/pjmedia-audiodev/wmme_dev.c 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/pjmedia/src/pjmedia-audiodev/wmme_dev.c 2021-02-06 22:51:16.641714862 +0100 +@@ -32,6 +32,7 @@ + #endif + + #include ++#include + #include + #include + +@@ -69,6 +70,15 @@ + + #define THIS_FILE "wmme_dev.c" + ++/* WMME device change observer */ ++struct wmme_dev_observer ++{ ++ pj_thread_t *thread; ++ pj_pool_t *pool; ++ pjmedia_aud_dev_change_callback cb; ++ HWND hWnd; ++}; ++ + /* WMME device info */ + struct wmme_dev_info + { +@@ -87,6 +97,8 @@ + + unsigned dev_count; + struct wmme_dev_info *dev_info; ++ ++ struct wmme_dev_observer dev_observer; + }; + + +@@ -151,6 +163,11 @@ + pjmedia_aud_play_cb play_cb, + void *user_data, + pjmedia_aud_stream **p_aud_strm); ++static void factory_set_observer(pjmedia_aud_dev_factory *f, ++ pjmedia_aud_dev_change_callback cb); ++static int factory_get_default_rec_dev(pjmedia_aud_dev_factory *f); ++static int factory_get_default_play_dev(pjmedia_aud_dev_factory *f); ++ + + static pj_status_t stream_get_param(pjmedia_aud_stream *strm, + pjmedia_aud_param *param); +@@ -174,7 +191,10 @@ + &factory_get_dev_info, + &factory_default_param, + &factory_create_stream, +- &factory_refresh ++ &factory_refresh, ++ &factory_set_observer, ++ &factory_get_default_rec_dev, ++ &factory_get_default_play_dev + }; + + static pjmedia_aud_stream_op stream_op = +@@ -1336,6 +1356,201 @@ + return PJ_SUCCESS; + } + ++/* Processes OS messages arriving at the hWnd window */ ++INT_PTR WINAPI ProcessOSMessage(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) ++{ ++ /* wf is used in order to query the number of audio devices currently handled */ ++ static struct wmme_factory *wf = NULL; ++ ++ switch( message ) ++ { ++ case WM_CREATE: ++ /* Initialize wf pointer on the first run */ ++ if (wf == NULL) ++ { ++ CREATESTRUCT *CrtStrPtr = (CREATESTRUCT *) lParam; ++ wf = (struct wmme_factory *)(CrtStrPtr->lpCreateParams); ++ } ++ break; ++ case WM_DEVICECHANGE: ++ /* Possible insertion or removal of device. There's some issues: ++ ++ - Some devices/drivers does not trigger arrival nor ++ removecomplete events, but only devnodes_changed events. ++ Therefore, we process all of those type of events. ++ ++ - Some hardware can send many devnodes_changed events at the ++ same time (up to ~15 of such events). These batches are ++ detected using temporal locality, using constMaxBatchPeriod_. ++ Once the device is detected, the rest of redundant events ++ are discarded. In order to know if there's a new device or not, ++ actual audio devices count is compared to stored audio devices ++ count (via wf->dev_count). ++ ++ - Hardware takes some time to settle and be recognized by ++ drivers. A small window of time is given in order to account ++ for this (constMaxSettleTime_); ++ ++ Settle time should be slightly lower than batch period. ++ */ ++ if (wParam == DBT_DEVICEARRIVAL || wParam == DBT_DEVICEREMOVECOMPLETE || wParam == DBT_DEVNODES_CHANGED) { ++ const int constMaxBatchPeriod_ = 3; /* seconds */ ++ const int constMaxSettleTime_ = (constMaxBatchPeriod_ * 1000) - 500; /* milliseconds */ ++ ++ /* Loop that allows hardware to settle */ ++ int settleTimeLeft = constMaxSettleTime_; ++ while (settleTimeLeft > 0) { ++ /* Check if actual devices lists (I/O) sizes have actually ++ changed before notifying upper levels. Consider input ++ devices, output devices and a WAVE MAPPER device for each. ++ */ ++ if(waveInGetNumDevs() + waveOutGetNumDevs() + 2 != wf->dev_count) { ++ /* Hardware changed */ ++ if (wf->dev_observer.cb) { ++ wf->dev_observer.cb(DEVICE_LIST_CHANGED); ++ } ++ break; ++ } else { ++ /* Hardware is settling... */ ++ Sleep(250); ++ settleTimeLeft -= 250; ++ } ++ } ++ } ++ break; ++ case WM_CLOSE: ++ if (!DestroyWindow(hWnd)) { ++ PJ_LOG(4,(THIS_FILE, "Couldn't destroy message window")); ++ } ++ break; ++ case WM_DESTROY: ++ PostQuitMessage(0); ++ break; ++ default: ++ break; ++ } ++ ++ return 1; ++} ++ ++static pj_status_t create_os_messages_window(struct wmme_factory *wf) ++{ ++ pj_status_t status = PJ_EBUG; ++ WNDCLASSEX wndClass; ++ HWND hWnd; ++ ++ /* Set up and register window class */ ++ ZeroMemory(&wndClass, sizeof(WNDCLASSEX)); ++ wndClass.cbSize = sizeof(WNDCLASSEX); ++ wndClass.style = CS_OWNDC; ++ wndClass.lpfnWndProc = (WNDPROC)(ProcessOSMessage); ++ wndClass.hInstance = (HINSTANCE)(GetModuleHandle(0)); ++ wndClass.lpszClassName = "DeviceChangeMessageWindow"; ++ ++ if (RegisterClassEx(&wndClass)) { ++ /* Create the window that will receive OS messages */ ++ hWnd = CreateWindowEx( 0, "DeviceChangeMessageWindow", NULL, 0, 0, 0, 0, 0, NULL, NULL, NULL, (LPVOID)(wf)); ++ if (hWnd != NULL) { ++ wf->dev_observer.hWnd = hWnd; ++ if (UpdateWindow(hWnd) != 0) { ++ status = PJ_SUCCESS; ++ } ++ } else { ++ PJ_LOG(4,(THIS_FILE, "Error creating window to receive device change events")); ++ } ++ } ++ ++ return status; ++ ++} ++ ++static pj_status_t dispatch_os_messages(void) ++{ ++ pj_status_t status = PJ_SUCCESS; ++ MSG msg; ++ int ret; ++ ++ /* Process OS messages with low cpu-usage wait loop */ ++ while((ret = GetMessage(&msg, NULL, 0, 0)) != 0) { ++ if (ret == -1) { ++ PJ_LOG(4,(THIS_FILE, "Couldn't process OS message")); ++ status = PJ_EBUG; ++ break; ++ } else { ++ TranslateMessage(&msg); ++ DispatchMessage(&msg); ++ } ++ } ++ ++ return status; ++ ++} ++ ++/* WMME device observer thread thread. */ ++static int PJ_THREAD_FUNC wmme_dev_observer_thread(void *arg) ++{ ++ struct wmme_factory *wf = (struct wmme_factory*)arg; ++ pj_status_t status; ++ ++ status = create_os_messages_window(wf); ++ if (status == PJ_SUCCESS) { ++ status = dispatch_os_messages(); ++ if (status != PJ_SUCCESS) { ++ PJ_LOG(4,(THIS_FILE, "Error dispatching device detection window events")); ++ } ++ } else { ++ PJ_LOG(4,(THIS_FILE, "Failed to create window for receiving device detection events")); ++ } ++ ++ return status; ++} ++ ++/* API: set audio device change observer */ ++static void factory_set_observer(pjmedia_aud_dev_factory *f, ++ pjmedia_aud_dev_change_callback cb) ++{ ++ struct wmme_factory *wf = (struct wmme_factory*)f; ++ pj_pool_t *pool; ++ pj_status_t status; ++ ++ if (cb) { ++ pool = pj_pool_create(wf->pf, "wmme-dev-observer", 1000, 1000, NULL); ++ PJ_ASSERT_ON_FAIL(pool != NULL, {return;}); ++ status = pj_thread_create(pool, "wmme_observer", &wmme_dev_observer_thread, wf, 0, 0, &wf->dev_observer.thread); ++ if (status != PJ_SUCCESS) { ++ PJ_LOG(4,(THIS_FILE, "Failed to create WMME device detection thread")); ++ wf->dev_observer.thread = NULL; ++ return; ++ } ++ wf->dev_observer.cb = cb; ++ } else { ++ wf->dev_observer.cb = NULL; ++ if (wf->dev_observer.hWnd) { ++ CloseWindow(wf->dev_observer.hWnd); ++ wf->dev_observer.hWnd = NULL; ++ } ++ pj_thread_join(wf->dev_observer.thread); ++ pj_thread_destroy(wf->dev_observer.thread); ++ wf->dev_observer.thread = NULL; ++ } ++} ++ ++/* API: get default recording device */ ++static int factory_get_default_rec_dev(pjmedia_aud_dev_factory *f) ++{ ++ PJ_UNUSED_ARG(f); ++ /* Let PJMEDIA pick the first one available */ ++ return -1; ++} ++ ++/* API: get default playback device */ ++static int factory_get_default_play_dev(pjmedia_aud_dev_factory *f) ++{ ++ PJ_UNUSED_ARG(f); ++ /* Let PJMEDIA pick the first one available */ ++ return -1; ++} ++ + /* API: Get stream info. */ + static pj_status_t stream_get_param(pjmedia_aud_stream *s, + pjmedia_aud_param *pi) +diff -ruN pjproject-2.10/pjmedia/src/pjmedia-codec/ffmpeg_vid_codecs.c pjsip/pjmedia/src/pjmedia-codec/ffmpeg_vid_codecs.c +--- pjproject-2.10/pjmedia/src/pjmedia-codec/ffmpeg_vid_codecs.c 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/pjmedia/src/pjmedia-codec/ffmpeg_vid_codecs.c 2021-02-06 22:51:16.641714862 +0100 +@@ -62,16 +62,7 @@ + #endif + + #if LIBAVCODEC_VER_AT_LEAST(53,61) +-# if LIBAVCODEC_VER_AT_LEAST(54,59) +- /* Not sure when AVCodec::encode is obsoleted/removed. */ +-# define AVCODEC_HAS_ENCODE(c) (c->encode2) +-# else +- /* Not sure when AVCodec::encode2 is introduced. It appears in +- * libavcodec 53.61 where some codecs actually still use AVCodec::encode +- * (e.g: H263, H264). +- */ +-# define AVCODEC_HAS_ENCODE(c) (c->encode || c->encode2) +-# endif ++# define AVCODEC_HAS_ENCODE(c) (c->encode2) + # define AV_OPT_SET(obj,name,val,opt) (av_opt_set(obj,name,val,opt)==0) + # define AV_OPT_SET_INT(obj,name,val) (av_opt_set_int(obj,name,val,0)==0) + #else +@@ -266,6 +257,7 @@ + /* H264 constants */ + #define PROFILE_H264_BASELINE 66 + #define PROFILE_H264_MAIN 77 ++#define PROFILE_H264_HIGH 100 + + /* Codec specific functions */ + #if PJMEDIA_HAS_FFMPEG_CODEC_H264 +@@ -404,6 +396,9 @@ + case PROFILE_H264_MAIN: + profile = "main"; + break; ++ case PROFILE_H264_HIGH: ++ profile = "high"; ++ break; + default: + break; + } +@@ -440,11 +435,11 @@ + /* Misc x264 settings (performance, quality, latency, etc). + * Let's just use the x264 predefined preset & tune. + */ +- if (!AV_OPT_SET(ctx->priv_data, "preset", "veryfast", 0)) { ++ if (!AV_OPT_SET(ctx->priv_data, "preset", "ultrafast", 0)) { + PJ_LOG(3, (THIS_FILE, "Failed to set x264 preset 'veryfast'")); + } +- if (!AV_OPT_SET(ctx->priv_data, "tune", "animation+zerolatency", 0)) { +- PJ_LOG(3, (THIS_FILE, "Failed to set x264 tune 'zerolatency'")); ++ if (!AV_OPT_SET(ctx->priv_data, "tune", "fastdecode+zerolatency", 0)) { ++ PJ_LOG(3, (THIS_FILE, "Failed to set x264 tune 'fastdecode+zerolatency'")); + } + } + +@@ -1705,12 +1700,12 @@ + avpacket.size = (int)input->size; + + /* ffmpeg warns: +- * - input buffer padding, at least FF_INPUT_BUFFER_PADDING_SIZE ++ * - input buffer padding, at least AV_INPUT_BUFFER_PADDING_SIZE + * - null terminated + * Normally, encoded buffer is allocated more than needed, so lets just + * bzero the input buffer end/pad, hope it will be just fine. + */ +- pj_bzero(avpacket.data+avpacket.size, FF_INPUT_BUFFER_PADDING_SIZE); ++ pj_bzero(avpacket.data+avpacket.size, AV_INPUT_BUFFER_PADDING_SIZE); + + output->bit_info = 0; + output->timestamp = input->timestamp; +diff -ruN pjproject-2.10/pjmedia/src/pjmedia-videodev/avf_dev.m pjsip/pjmedia/src/pjmedia-videodev/avf_dev.m +--- pjproject-2.10/pjmedia/src/pjmedia-videodev/avf_dev.m 1970-01-01 01:00:00.000000000 +0100 ++++ pjsip/pjmedia/src/pjmedia-videodev/avf_dev.m 2021-02-06 18:46:39.045322874 +0100 +@@ -0,0 +1,682 @@ ++/* ++ * Copyright (C) 2014-present AG Projects (http://ag-projects.com) ++ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ++ */ ++#include ++#include ++#include ++#include ++ ++#if defined(PJMEDIA_HAS_VIDEO) && PJMEDIA_HAS_VIDEO != 0 && \ ++ defined(PJMEDIA_VIDEO_DEV_HAS_AVF) && PJMEDIA_VIDEO_DEV_HAS_AVF != 0 ++ ++#include ++#include ++#include ++#include ++ ++#define THIS_FILE "avf_dev.c" ++#define DEFAULT_CLOCK_RATE 90000 ++#define DEFAULT_WIDTH 640 ++#define DEFAULT_HEIGHT 480 ++#define DEFAULT_FPS 15 ++ ++ ++typedef struct avf_fmt_info ++{ ++ pjmedia_format_id pjmedia_format; ++ unsigned avf_format; ++} avf_fmt_info; ++ ++static avf_fmt_info avf_fmts[] = ++{ ++ {PJMEDIA_FORMAT_BGRA, kCVPixelFormatType_32BGRA}, ++ {PJMEDIA_FORMAT_YUY2, kCVPixelFormatType_422YpCbCr8_yuvs}, ++ {PJMEDIA_FORMAT_UYVY, kCVPixelFormatType_422YpCbCr8}, ++}; ++ ++/* avf device info */ ++struct avf_dev_info ++{ ++ pjmedia_vid_dev_info info; ++ AVCaptureDevice *dev; ++}; ++ ++/* avf factory */ ++struct avf_factory ++{ ++ pjmedia_vid_dev_factory base; ++ pj_pool_t *pool; ++ pj_pool_t *dev_pool; ++ pj_pool_factory *pf; ++ ++ unsigned dev_count; ++ struct avf_dev_info *dev_info; ++}; ++ ++struct avf_stream; /* forward declaration */ ++typedef void (*func_ptr)(struct avf_stream *strm); ++ ++@interface AVFDelegate: NSObject ++{ ++@public ++ struct avf_stream *stream; ++} ++@end ++ ++ ++/* Video stream. */ ++struct avf_stream ++{ ++ pjmedia_vid_dev_stream base; /**< Base stream */ ++ pjmedia_vid_dev_param param; /**< Settings */ ++ pj_pool_t *pool; /**< Memory pool. */ ++ ++ pj_timestamp cap_frame_ts; /**< Captured frame tstamp */ ++ unsigned cap_ts_inc; /**< Increment */ ++ ++ pjmedia_vid_dev_cb vid_cb; /**< Stream callback. */ ++ void *user_data; /**< Application data. */ ++ ++ pjmedia_rect_size size; ++ ++ pj_bool_t cap_thread_initialized; ++ pj_thread_desc cap_thread_desc; ++ pj_thread_t *cap_thread; ++ pj_bool_t cap_exited; ++ ++ struct avf_factory *af; ++ pj_status_t status; ++ pj_bool_t is_running; ++ ++ dispatch_queue_t video_ops_queue; ++ ++ AVCaptureSession *cap_session; ++ AVCaptureDeviceInput *dev_input; ++ AVCaptureVideoDataOutput *video_output; ++ AVFDelegate *delegate; ++}; ++ ++ ++/* Prototypes */ ++static pj_status_t avf_factory_init(pjmedia_vid_dev_factory *f); ++static pj_status_t avf_factory_destroy(pjmedia_vid_dev_factory *f); ++static pj_status_t avf_factory_refresh(pjmedia_vid_dev_factory *f); ++static unsigned avf_factory_get_dev_count(pjmedia_vid_dev_factory *f); ++static pj_status_t avf_factory_get_dev_info(pjmedia_vid_dev_factory *f, ++ unsigned index, ++ pjmedia_vid_dev_info *info); ++static pj_status_t avf_factory_default_param(pj_pool_t *pool, ++ pjmedia_vid_dev_factory *f, ++ unsigned index, ++ pjmedia_vid_dev_param *param); ++static pj_status_t avf_factory_create_stream(pjmedia_vid_dev_factory *f, ++ pjmedia_vid_dev_param *param, ++ const pjmedia_vid_dev_cb *cb, ++ void *user_data, ++ pjmedia_vid_dev_stream **p_vid_strm); ++ ++static pj_status_t avf_stream_get_param(pjmedia_vid_dev_stream *strm, ++ pjmedia_vid_dev_param *param); ++static pj_status_t avf_stream_get_cap(pjmedia_vid_dev_stream *strm, ++ pjmedia_vid_dev_cap cap, ++ void *value); ++static pj_status_t avf_stream_set_cap(pjmedia_vid_dev_stream *strm, ++ pjmedia_vid_dev_cap cap, ++ const void *value); ++static pj_status_t avf_stream_start(pjmedia_vid_dev_stream *strm); ++static pj_status_t avf_stream_stop(pjmedia_vid_dev_stream *strm); ++static pj_status_t avf_stream_destroy(pjmedia_vid_dev_stream *strm); ++ ++/* Operations */ ++static pjmedia_vid_dev_factory_op factory_op = ++{ ++ &avf_factory_init, ++ &avf_factory_destroy, ++ &avf_factory_get_dev_count, ++ &avf_factory_get_dev_info, ++ &avf_factory_default_param, ++ &avf_factory_create_stream, ++ &avf_factory_refresh ++}; ++ ++static pjmedia_vid_dev_stream_op stream_op = ++{ ++ &avf_stream_get_param, ++ &avf_stream_get_cap, ++ &avf_stream_set_cap, ++ &avf_stream_start, ++ NULL, ++ NULL, ++ &avf_stream_stop, ++ &avf_stream_destroy ++}; ++ ++ ++/**************************************************************************** ++ * Factory operations ++ */ ++/* ++ * Init avf video driver. ++ */ ++pjmedia_vid_dev_factory* pjmedia_avf_factory(pj_pool_factory *pf) ++{ ++ struct avf_factory *f; ++ pj_pool_t *pool; ++ ++ pool = pj_pool_create(pf, "avf video", 4000, 4000, NULL); ++ f = PJ_POOL_ZALLOC_T(pool, struct avf_factory); ++ f->pf = pf; ++ f->pool = pool; ++ f->base.op = &factory_op; ++ ++ return &f->base; ++} ++ ++ ++/* API: init factory */ ++static pj_status_t avf_factory_init(pjmedia_vid_dev_factory *f) ++{ ++ return avf_factory_refresh(f); ++} ++ ++/* API: destroy factory */ ++static pj_status_t avf_factory_destroy(pjmedia_vid_dev_factory *f) ++{ ++ struct avf_factory *af = (struct avf_factory*)f; ++ pj_pool_t *pool = af->pool; ++ ++ if (af->dev_pool) ++ pj_pool_release(af->dev_pool); ++ af->pool = NULL; ++ if (pool) ++ pj_pool_release(pool); ++ ++ return PJ_SUCCESS; ++} ++ ++/* API: refresh the list of devices */ ++static pj_status_t avf_factory_refresh(pjmedia_vid_dev_factory *f) ++{ ++ struct avf_factory *af = (struct avf_factory*)f; ++ struct avf_dev_info *di; ++ unsigned dev_count = 0; ++ NSAutoreleasePool *apool = [[NSAutoreleasePool alloc]init]; ++ NSArray *dev_array; ++ ++ if (af->dev_pool) { ++ pj_pool_release(af->dev_pool); ++ af->dev_pool = NULL; ++ } ++ ++ dev_array = [AVCaptureDevice devices]; ++ for (AVCaptureDevice *device in dev_array) { ++ if ([device hasMediaType:AVMediaTypeVideo] && ![device isSuspended]) { ++ dev_count++; ++ } ++ } ++ ++ /* Initialize input and output devices here */ ++ af->dev_count = 0; ++ af->dev_pool = pj_pool_create(af->pf, "avf video", 500, 500, NULL); ++ ++ af->dev_info = (struct avf_dev_info*) pj_pool_calloc(af->dev_pool, dev_count, sizeof(struct avf_dev_info)); ++ for (AVCaptureDevice *device in dev_array) { ++ if (![device hasMediaType:AVMediaTypeVideo] || [device isSuspended]) { ++ continue; ++ } ++ ++ di = &af->dev_info[af->dev_count++]; ++ pj_bzero(di, sizeof(*di)); ++ di->dev = device; ++ pj_ansi_strncpy(di->info.name, [device.localizedName UTF8String], sizeof(di->info.name)); ++ pj_ansi_strncpy(di->info.driver, "AVF", sizeof(di->info.driver)); ++ di->info.dir = PJMEDIA_DIR_CAPTURE; ++ di->info.has_callback = PJ_TRUE; ++ di->info.fmt_cnt = 0; ++ di->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT; ++ ++ PJ_LOG(4, (THIS_FILE, " dev: %s", di->info.name)); ++ ++ for (AVCaptureDeviceFormat* f in [device formats]) { ++ unsigned i; ++ CMFormatDescriptionRef desc = [f formatDescription]; ++ for (i = 0; i < PJ_ARRAY_SIZE(avf_fmts); i++) { ++ if (CMFormatDescriptionGetMediaSubType(desc) == avf_fmts[i].avf_format) { ++ char fmt_name[5]; ++ CMVideoDimensions dim = CMVideoFormatDescriptionGetDimensions(desc); ++ if (dim.width < 640) ++ continue; ++ pjmedia_fourcc_name(avf_fmts[i].pjmedia_format, fmt_name); ++ PJ_LOG(4, (THIS_FILE, " detected resolution %dx%d (%s)", dim.width, dim.height, fmt_name)); ++ pjmedia_format *fmt = &di->info.fmt[di->info.fmt_cnt++]; ++ pjmedia_format_init_video(fmt, ++ avf_fmts[i].pjmedia_format, ++ dim.width, ++ dim.height, ++ DEFAULT_FPS, 1); ++ } ++ } ++ } ++ ++ if (di->info.fmt_cnt == 0) { ++ PJ_LOG(4, (THIS_FILE, " there are no compatible formats, using default")); ++ pjmedia_format *fmt = &di->info.fmt[di->info.fmt_cnt++]; ++ pjmedia_format_init_video(fmt, ++ avf_fmts[0].pjmedia_format, ++ DEFAULT_WIDTH, ++ DEFAULT_HEIGHT, ++ DEFAULT_FPS, 1); ++ } ++ } ++ ++ [apool release]; ++ ++ PJ_LOG(4, (THIS_FILE, "avf video has %d devices", af->dev_count)); ++ ++ return PJ_SUCCESS; ++} ++ ++/* API: get number of devices */ ++static unsigned avf_factory_get_dev_count(pjmedia_vid_dev_factory *f) ++{ ++ struct avf_factory *af = (struct avf_factory*)f; ++ return af->dev_count; ++} ++ ++/* API: get device info */ ++static pj_status_t avf_factory_get_dev_info(pjmedia_vid_dev_factory *f, ++ unsigned index, ++ pjmedia_vid_dev_info *info) ++{ ++ struct avf_factory *af = (struct avf_factory*)f; ++ PJ_ASSERT_RETURN(index < af->dev_count, PJMEDIA_EVID_INVDEV); ++ ++ pj_memcpy(info, &af->dev_info[index].info, sizeof(*info)); ++ ++ return PJ_SUCCESS; ++} ++ ++/* API: create default device parameter */ ++static pj_status_t avf_factory_default_param(pj_pool_t *pool, ++ pjmedia_vid_dev_factory *f, ++ unsigned index, ++ pjmedia_vid_dev_param *param) ++{ ++ struct avf_factory *af = (struct avf_factory*)f; ++ struct avf_dev_info *di = &af->dev_info[index]; ++ ++ PJ_ASSERT_RETURN(index < af->dev_count, PJMEDIA_EVID_INVDEV); ++ PJ_UNUSED_ARG(pool); ++ ++ pj_bzero(param, sizeof(*param)); ++ param->dir = PJMEDIA_DIR_CAPTURE; ++ param->cap_id = index; ++ param->rend_id = PJMEDIA_VID_INVALID_DEV; ++ param->flags = PJMEDIA_VID_DEV_CAP_FORMAT; ++ param->clock_rate = DEFAULT_CLOCK_RATE; ++ pj_memcpy(¶m->fmt, &di->info.fmt[0], sizeof(param->fmt)); ++ ++ return PJ_SUCCESS; ++} ++ ++static avf_fmt_info* get_avf_format_info(pjmedia_format_id id) ++{ ++ unsigned i; ++ ++ for (i = 0; i < PJ_ARRAY_SIZE(avf_fmts); i++) { ++ if (avf_fmts[i].pjmedia_format == id) ++ return &avf_fmts[i]; ++ } ++ ++ return NULL; ++} ++ ++ ++@implementation AVFDelegate ++- (void)captureOutput:(AVCaptureOutput *)captureOutput ++ didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer ++ fromConnection:(AVCaptureConnection *)connection ++{ ++ pjmedia_frame frame = {0}; ++ CVImageBufferRef img; ++ CVReturn ret; ++ OSType type; ++ size_t width, height; ++ ++ /* Register thread if needed */ ++ if (stream->cap_thread_initialized == 0 || !pj_thread_is_registered()) { ++ pj_bzero(stream->cap_thread_desc, sizeof(pj_thread_desc)); ++ pj_thread_register("avf_cap", stream->cap_thread_desc, &stream->cap_thread); ++ stream->cap_thread_initialized = 1; ++ } ++ ++ if (!sampleBuffer) ++ return; ++ ++ /* Get a CMSampleBuffer's Core Video image buffer for the media data */ ++ img = CMSampleBufferGetImageBuffer(sampleBuffer); ++ if (!img) ++ return; ++ ++ /* Check for supported formats */ ++ type = CVPixelBufferGetPixelFormatType(img); ++ switch(type) { ++ case kCVPixelFormatType_32BGRA: ++ case kCVPixelFormatType_422YpCbCr8_yuvs: ++ case kCVPixelFormatType_422YpCbCr8: ++ break; ++ default: ++ PJ_LOG(4, (THIS_FILE, "Unsupported image format! %c%c%c%c", type>>24, type>>16, type>>8, type>>0)); ++ return; ++ } ++ ++ /* Lock the base address of the pixel buffer */ ++ ret = CVPixelBufferLockBaseAddress(img, kCVPixelBufferLock_ReadOnly); ++ if (ret != kCVReturnSuccess) ++ return; ++ ++ width = CVPixelBufferGetWidth(img); ++ height = CVPixelBufferGetHeight(img); ++ ++ /* Prepare frame */ ++ frame.type = PJMEDIA_FRAME_TYPE_VIDEO; ++ frame.timestamp.u64 = stream->cap_frame_ts.u64; ++ frame.buf = CVPixelBufferGetBaseAddress(img); ++ frame.size = CVPixelBufferGetBytesPerRow(img) * height; ++ ++ if (stream->size.w != width || stream->size.h != height) { ++ PJ_LOG(4, (THIS_FILE, "AVF image size changed, before: %dx%d, after: %dx%d", stream->size.w, stream->size.h, width, height)); ++ } ++ ++ if (stream->vid_cb.capture_cb) { ++ (*stream->vid_cb.capture_cb)(&stream->base, stream->user_data, &frame); ++ } ++ ++ stream->cap_frame_ts.u64 += stream->cap_ts_inc; ++ ++ /* Unlock the pixel buffer */ ++ CVPixelBufferUnlockBaseAddress(img, kCVPixelBufferLock_ReadOnly); ++} ++@end ++ ++ ++static void init_avf_stream(struct avf_stream *strm) ++{ ++ const pjmedia_video_format_info *vfi; ++ pjmedia_video_format_detail *vfd; ++ avf_fmt_info *fi = get_avf_format_info(strm->param.fmt.id); ++ NSError *error; ++ pj_status_t status; ++ ++ if (!fi) { ++ strm->status = PJMEDIA_EVID_BADFORMAT; ++ return; ++ } ++ ++ strm->cap_session = [[AVCaptureSession alloc] init]; ++ if (!strm->cap_session) { ++ strm->status = PJ_ENOMEM; ++ return; ++ } ++ ++ strm->cap_session.sessionPreset = AVCaptureSessionPresetHigh; ++ vfd = pjmedia_format_get_video_format_detail(&strm->param.fmt, PJ_TRUE); ++ pj_assert(vfd); ++ vfi = pjmedia_get_video_format_info(NULL, strm->param.fmt.id); ++ pj_assert(vfi); ++ vfd->size = strm->size; ++ ++ PJ_LOG(4, (THIS_FILE, "Opening video device at %dx%d resolution", vfd->size.w, vfd->size.h)); ++ ++ /* Add the video device to the session as a device input */ ++ AVCaptureDevice *videoDevice = strm->af->dev_info[strm->param.cap_id].dev; ++ strm->dev_input = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error: &error]; ++ if (!strm->dev_input) { ++ status = PJMEDIA_EVID_SYSERR; ++ return; ++ } ++ ++ [strm->cap_session addInput:strm->dev_input]; ++ ++ strm->video_output = [[AVCaptureVideoDataOutput alloc] init]; ++ if (!strm->video_output) { ++ status = PJMEDIA_EVID_SYSERR; ++ return; ++ } ++ [strm->cap_session addOutput:strm->video_output]; ++ ++ /* Configure the video output */ ++ strm->video_output.alwaysDiscardsLateVideoFrames = YES; ++ /* The Apple provided documentation says the only supported key is kCVPixelBufferPixelFormatTypeKey, ++ * but it turns out kCVPixelBufferWidthKey and kCVPixelBufferHeightKey are also required. Thanks ++ * Chromium, for figuring it out.*/ ++ strm->video_output.videoSettings = ++ [NSDictionary dictionaryWithObjectsAndKeys: @(fi->avf_format), ++ kCVPixelBufferPixelFormatTypeKey, ++ @(vfd->size.w), ++ kCVPixelBufferWidthKey, ++ @(vfd->size.h), ++ kCVPixelBufferHeightKey, ++ nil]; ++ strm->delegate = [[AVFDelegate alloc] init]; ++ strm->delegate->stream = strm; ++ dispatch_queue_t queue = dispatch_queue_create("AVFQueue", NULL); ++ [strm->video_output setSampleBufferDelegate:strm->delegate queue:queue]; ++ dispatch_release(queue); ++} ++ ++static void run_func_on_video_queue(struct avf_stream *strm, func_ptr func) ++{ ++ dispatch_sync(strm->video_ops_queue, ^{ ++ (*func)(strm); ++ }); ++} ++ ++/* API: create stream */ ++static pj_status_t avf_factory_create_stream(pjmedia_vid_dev_factory *f, ++ pjmedia_vid_dev_param *param, ++ const pjmedia_vid_dev_cb *cb, ++ void *user_data, ++ pjmedia_vid_dev_stream **p_vid_strm) ++{ ++ struct avf_factory *af = (struct avf_factory*)f; ++ pj_pool_t *pool; ++ struct avf_stream *strm; ++ const pjmedia_video_format_info *vfi; ++ pjmedia_video_format_detail *vfd; ++ pj_status_t status = PJ_SUCCESS; ++ ++ PJ_ASSERT_RETURN(f && param && p_vid_strm, PJ_EINVAL); ++ PJ_ASSERT_RETURN(param->fmt.type == PJMEDIA_TYPE_VIDEO && ++ param->fmt.detail_type == PJMEDIA_FORMAT_DETAIL_VIDEO && ++ param->dir == PJMEDIA_DIR_CAPTURE, ++ PJ_EINVAL); ++ ++ vfi = pjmedia_get_video_format_info(NULL, param->fmt.id); ++ if (!vfi) ++ return PJMEDIA_EVID_BADFORMAT; ++ ++ /* Create and Initialize stream descriptor */ ++ pool = pj_pool_create(af->pf, "avf-dev", 4000, 4000, NULL); ++ PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); ++ ++ strm = PJ_POOL_ZALLOC_T(pool, struct avf_stream); ++ pj_memcpy(&strm->param, param, sizeof(*param)); ++ strm->pool = pool; ++ pj_memcpy(&strm->vid_cb, cb, sizeof(*cb)); ++ strm->user_data = user_data; ++ strm->af = af; ++ ++ vfd = pjmedia_format_get_video_format_detail(&strm->param.fmt, PJ_TRUE); ++ pj_memcpy(&strm->size, &vfd->size, sizeof(vfd->size)); ++ pj_assert(vfd->fps.num); ++ strm->cap_ts_inc = PJMEDIA_SPF2(strm->param.clock_rate, &vfd->fps, 1); ++ ++ /* Create dispatch queue */ ++ strm->video_ops_queue = dispatch_queue_create("AVF Video Ops", DISPATCH_QUEUE_SERIAL); ++ ++ /* Create capture stream here */ ++ strm->status = PJ_SUCCESS; ++ run_func_on_video_queue(strm, init_avf_stream); ++ status = strm->status; ++ if (status != PJ_SUCCESS) { ++ dispatch_release(strm->video_ops_queue); ++ avf_stream_destroy((pjmedia_vid_dev_stream *)strm); ++ return status; ++ } ++ ++ /* Update param as output */ ++ param->fmt = strm->param.fmt; ++ ++ /* Done */ ++ strm->base.op = &stream_op; ++ *p_vid_strm = &strm->base; ++ ++ return PJ_SUCCESS; ++} ++ ++/* API: Get stream info. */ ++static pj_status_t avf_stream_get_param(pjmedia_vid_dev_stream *s, ++ pjmedia_vid_dev_param *pi) ++{ ++ struct avf_stream *strm = (struct avf_stream*)s; ++ PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL); ++ ++ pj_memcpy(pi, &strm->param, sizeof(*pi)); ++ ++ return PJ_SUCCESS; ++} ++ ++/* API: get capability */ ++static pj_status_t avf_stream_get_cap(pjmedia_vid_dev_stream *s, ++ pjmedia_vid_dev_cap cap, ++ void *pval) ++{ ++ struct avf_stream *strm = (struct avf_stream*)s; ++ ++ PJ_UNUSED_ARG(strm); ++ PJ_UNUSED_ARG(cap); ++ PJ_UNUSED_ARG(pval); ++ ++ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); ++ ++ return PJMEDIA_EVID_INVCAP; ++} ++ ++/* API: set capability */ ++static pj_status_t avf_stream_set_cap(pjmedia_vid_dev_stream *s, ++ pjmedia_vid_dev_cap cap, ++ const void *pval) ++{ ++ struct avf_stream *strm = (struct avf_stream*)s; ++ ++ PJ_UNUSED_ARG(strm); ++ PJ_UNUSED_ARG(cap); ++ PJ_UNUSED_ARG(pval); ++ ++ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); ++ ++ return PJMEDIA_EVID_INVCAP; ++} ++ ++static void start_avf(struct avf_stream *strm) ++{ ++ [strm->cap_session startRunning]; ++} ++ ++static void stop_avf(struct avf_stream *strm) ++{ ++ [strm->cap_session stopRunning]; ++} ++ ++/* API: Start stream. */ ++static pj_status_t avf_stream_start(pjmedia_vid_dev_stream *strm) ++{ ++ struct avf_stream *stream = (struct avf_stream*)strm; ++ ++ PJ_LOG(4, (THIS_FILE, "Starting avf video stream")); ++ ++ if (stream->cap_session) { ++ run_func_on_video_queue(stream, start_avf); ++ if (![stream->cap_session isRunning]) ++ return PJMEDIA_EVID_NOTREADY; ++ stream->is_running = PJ_TRUE; ++ } ++ ++ return PJ_SUCCESS; ++} ++ ++/* API: Stop stream. */ ++static pj_status_t avf_stream_stop(pjmedia_vid_dev_stream *strm) ++{ ++ struct avf_stream *stream = (struct avf_stream*)strm; ++ ++ PJ_LOG(4, (THIS_FILE, "Stopping avf video stream")); ++ ++ if (stream->cap_session && [stream->cap_session isRunning]) { ++ int i; ++ stream->cap_exited = PJ_FALSE; ++ run_func_on_video_queue(stream, stop_avf); ++ stream->is_running = PJ_FALSE; ++ for (i = 50; i >= 0 && !stream->cap_exited; i--) { ++ pj_thread_sleep(10); ++ } ++ } ++ ++ return PJ_SUCCESS; ++} ++ ++static void destroy_avf(struct avf_stream *strm) ++{ ++ if (strm->cap_session) { ++ [strm->cap_session removeInput:strm->dev_input]; ++ [strm->cap_session removeOutput:strm->video_output]; ++ [strm->cap_session release]; ++ strm->cap_session = NULL; ++ } ++ ++ if (strm->delegate) { ++ [strm->delegate release]; ++ strm->delegate = NULL; ++ } ++ ++ if (strm->dev_input) { ++ strm->dev_input = NULL; ++ } ++ if (strm->video_output) { ++ strm->video_output = NULL; ++ } ++} ++ ++/* API: Destroy stream. */ ++static pj_status_t avf_stream_destroy(pjmedia_vid_dev_stream *strm) ++{ ++ struct avf_stream *stream = (struct avf_stream*)strm; ++ ++ PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); ++ ++ avf_stream_stop(strm); ++ run_func_on_video_queue(stream, destroy_avf); ++ ++ dispatch_release(stream->video_ops_queue); ++ pj_pool_release(stream->pool); ++ ++ return PJ_SUCCESS; ++} ++ ++#endif /* PJMEDIA_VIDEO_DEV_HAS_AVF */ +diff -ruN pjproject-2.10/pjmedia/src/pjmedia-videodev/dshow_dev.c pjsip/pjmedia/src/pjmedia-videodev/dshow_dev.c +--- pjproject-2.10/pjmedia/src/pjmedia-videodev/dshow_dev.c 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/pjmedia/src/pjmedia-videodev/dshow_dev.c 2021-02-06 22:51:16.641714862 +0100 +@@ -20,30 +20,24 @@ + #include + #include + #include +-#include + + + #if defined(PJMEDIA_HAS_VIDEO) && PJMEDIA_HAS_VIDEO != 0 && \ + defined(PJMEDIA_VIDEO_DEV_HAS_DSHOW) && PJMEDIA_VIDEO_DEV_HAS_DSHOW != 0 + +- +-#ifdef _MSC_VER +-# pragma warning(push, 3) +-#endif +- + #include + #define COBJMACROS + #include + #include ++#include + +-#ifdef _MSC_VER +-# pragma warning(pop) ++#ifndef DIBSIZE ++# define WIDTHBYTES(BTIS) ((DWORD)(((BTIS)+31) & (~31)) / 8) ++# define DIBWIDTHBYTES(BI) (DWORD)(BI).biBitCount) * (DWORD)WIDTHBYTES((DWORD)(BI).biWidth ++# define _DIBSIZE(BI) (DIBWIDTHBYTES(BI) * (DWORD)(BI).biHeight) ++# define DIBSIZE(BI) ((BI).biHeight < 0 ? (-1)*(_DIBSIZE(BI)) : _DIBSIZE(BI)) + #endif + +-#pragma comment(lib, "Strmiids.lib") +-#pragma comment(lib, "Rpcrt4.lib") +-#pragma comment(lib, "Quartz.lib") +- + #define THIS_FILE "dshow_dev.c" + #define DEFAULT_CLOCK_RATE 90000 + #define DEFAULT_WIDTH 640 +@@ -57,10 +51,6 @@ + typedef struct NullRenderer NullRenderer; + IBaseFilter* NullRenderer_Create(input_callback input_cb, + void *user_data); +-typedef struct SourceFilter SourceFilter; +-IBaseFilter* SourceFilter_Create(SourceFilter **pSrc); +-HRESULT SourceFilter_Deliver(SourceFilter *src, void *buf, long size); +-void SourceFilter_SetMediaType(SourceFilter *src, AM_MEDIA_TYPE *pmt); + + typedef struct dshow_fmt_info + { +@@ -121,7 +111,6 @@ + { + IFilterGraph *filter_graph; + IMediaFilter *media_filter; +- SourceFilter *csource_filter; + IBaseFilter *source_filter; + IBaseFilter *rend_filter; + AM_MEDIA_TYPE *mediatype; +@@ -160,8 +149,6 @@ + pjmedia_vid_dev_cap cap, + const void *value); + static pj_status_t dshow_stream_start(pjmedia_vid_dev_stream *strm); +-static pj_status_t dshow_stream_put_frame(pjmedia_vid_dev_stream *strm, +- const pjmedia_frame *frame); + static pj_status_t dshow_stream_stop(pjmedia_vid_dev_stream *strm); + static pj_status_t dshow_stream_destroy(pjmedia_vid_dev_stream *strm); + +@@ -184,7 +171,7 @@ + &dshow_stream_set_cap, + &dshow_stream_start, + NULL, +- &dshow_stream_put_frame, ++ NULL, + &dshow_stream_stop, + &dshow_stream_destroy + }; +@@ -213,19 +200,12 @@ + /* API: init factory */ + static pj_status_t dshow_factory_init(pjmedia_vid_dev_factory *f) + { +- HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); +- if (hr == RPC_E_CHANGED_MODE) { +- /* When using apartment mode, Dshow object would not be accessible from +- * other thread. Take this into consideration when implementing native +- * renderer using Dshow. +- */ +- hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); +- if (FAILED(hr)) { +- PJ_LOG(4,(THIS_FILE, "Failed initializing DShow: " +- "COM library already initialized with " +- "incompatible concurrency model")); +- return PJMEDIA_EVID_INIT; +- } ++ HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); ++ if (hr == RPC_E_CHANGED_MODE) { ++ PJ_LOG(4,(THIS_FILE, "Failed initializing DShow: " ++ "COM library already initialized with " ++ "incompatible concurrency model")); ++ return PJMEDIA_EVID_INIT; + } + + return dshow_factory_refresh(f); +@@ -459,13 +439,21 @@ + if (SUCCEEDED(hr) && var_name.bstrVal) { + WCHAR *wszDisplayName = NULL; + IBaseFilter *filter; ++ pj_ssize_t len; + + ddi = &df->dev_info[df->dev_count++]; + pj_bzero(ddi, sizeof(*ddi)); +- pj_unicode_to_ansi(var_name.bstrVal, +- wcslen(var_name.bstrVal), +- ddi->info.name, +- sizeof(ddi->info.name)); ++ ++ len = wcslen(var_name.bstrVal), ++ len = WideCharToMultiByte(CP_ACP, ++ 0, ++ var_name.bstrVal, ++ (int)len, ++ ddi->info.name, ++ sizeof(ddi->info.name), ++ NULL, ++ NULL); ++ ddi->info.name[len] = '\0'; + + hr = IMoniker_GetDisplayName(moniker, NULL, NULL, + &wszDisplayName); +@@ -502,26 +490,6 @@ + ICreateDevEnum_Release(dev_enum); + } + +-#if HAS_VMR +- ddi = &df->dev_info[df->dev_count++]; +- pj_bzero(ddi, sizeof(*ddi)); +- pj_ansi_strncpy(ddi->info.name, "Video Mixing Renderer", +- sizeof(ddi->info.name)); +- ddi->info.name[sizeof(ddi->info.name)-1] = '\0'; +- pj_ansi_strncpy(ddi->info.driver, "dshow", sizeof(ddi->info.driver)); +- ddi->info.driver[sizeof(ddi->info.driver)-1] = '\0'; +- ddi->info.dir = PJMEDIA_DIR_RENDER; +- ddi->info.has_callback = PJ_FALSE; +- ddi->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT; +-// TODO: +-// ddi->info.caps |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW; +- +- ddi->info.fmt_cnt = 1; +- pjmedia_format_init_video(&ddi->info.fmt[0], dshow_fmts[0].pjmedia_format, +- DEFAULT_WIDTH, DEFAULT_HEIGHT, +- DEFAULT_FPS, 1); +-#endif +- + PJ_LOG(4, (THIS_FILE, "DShow has %d devices:", + df->dev_count)); + for (c = 0; c < df->dev_count; ++c) { +@@ -574,10 +542,6 @@ + param->dir = PJMEDIA_DIR_CAPTURE; + param->cap_id = index; + param->rend_id = PJMEDIA_VID_INVALID_DEV; +- } else if (di->info.dir & PJMEDIA_DIR_RENDER) { +- param->dir = PJMEDIA_DIR_RENDER; +- param->rend_id = index; +- param->cap_id = PJMEDIA_VID_INVALID_DEV; + } else { + return PJMEDIA_EVID_INVDEV; + } +@@ -645,26 +609,6 @@ + (*strm->vid_cb.capture_cb)(&strm->base, strm->user_data, &frame); + } + +-/* API: Put frame from stream */ +-static pj_status_t dshow_stream_put_frame(pjmedia_vid_dev_stream *strm, +- const pjmedia_frame *frame) +-{ +- struct dshow_stream *stream = (struct dshow_stream*)strm; +- HRESULT hr; +- +- if (stream->quit_flag) { +- stream->rend_thread_exited = PJ_TRUE; +- return PJ_SUCCESS; +- } +- +- hr = SourceFilter_Deliver(stream->dgraph.csource_filter, +- frame->buf, (long)frame->size); +- if (FAILED(hr)) +- return hr; +- +- return PJ_SUCCESS; +-} +- + static dshow_fmt_info* get_dshow_format_info(pjmedia_format_id id) + { + unsigned i; +@@ -689,16 +633,24 @@ + IEnumPins *pEnum; + IPin *srcpin = NULL; + IPin *sinkpin = NULL; +- AM_MEDIA_TYPE *mediatype= NULL, mtype; ++ AM_MEDIA_TYPE *mediatype = NULL; + VIDEOINFOHEADER *video_info, *vi = NULL; + pjmedia_video_format_detail *vfd; + const pjmedia_video_format_info *vfi; + ++ PJ_ASSERT_RETURN(dir == PJMEDIA_DIR_CAPTURE, PJ_EINVAL); ++ + vfi = pjmedia_get_video_format_info(pjmedia_video_format_mgr_instance(), + strm->param.fmt.id); + if (!vfi) + return PJMEDIA_EVID_BADFORMAT; + ++ hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); ++ if (FAILED(hr)) { ++ PJ_LOG(4,(THIS_FILE, "Error: CoInitializeEx")); ++ goto on_error; ++ } ++ + hr = CoCreateInstance(&CLSID_FilterGraph, NULL, CLSCTX_INPROC, + &IID_IFilterGraph, (LPVOID *)&graph->filter_graph); + if (FAILED(hr)) { +@@ -711,14 +663,10 @@ + goto on_error; + } + +- if (dir == PJMEDIA_DIR_CAPTURE) { + hr = get_cap_device(df, id, &graph->source_filter); + if (FAILED(hr)) { + goto on_error; + } +- } else { +- graph->source_filter = SourceFilter_Create(&graph->csource_filter); +- } + + hr = IFilterGraph_AddFilter(graph->filter_graph, graph->source_filter, + L"capture"); +@@ -726,16 +674,7 @@ + goto on_error; + } + +- if (dir == PJMEDIA_DIR_CAPTURE) { +- graph->rend_filter = NullRenderer_Create(input_cb, strm); +- } else { +- hr = CoCreateInstance(&CLSID_VideoMixingRenderer, NULL, +- CLSCTX_INPROC, &IID_IBaseFilter, +- (LPVOID *)&graph->rend_filter); +- if (FAILED (hr)) { +- goto on_error; +- } +- } ++ graph->rend_filter = NullRenderer_Create(input_cb, strm); + + IBaseFilter_EnumPins(graph->rend_filter, &pEnum); + if (SUCCEEDED(hr)) { +@@ -763,34 +702,6 @@ + (use_def_size? 0: vfd->size.h), &srcpin, NULL); + graph->mediatype = mediatype; + +- if (srcpin && dir == PJMEDIA_DIR_RENDER) { +- mediatype = graph->mediatype = &mtype; +- +- memset (mediatype, 0, sizeof(AM_MEDIA_TYPE)); +- mediatype->majortype = MEDIATYPE_Video; +- mediatype->subtype = *(get_dshow_format_info(strm->param.fmt.id)-> +- dshow_format); +- mediatype->bFixedSizeSamples = TRUE; +- mediatype->bTemporalCompression = FALSE; +- +- vi = (VIDEOINFOHEADER *) +- CoTaskMemAlloc(sizeof(VIDEOINFOHEADER)); +- memset (vi, 0, sizeof(VIDEOINFOHEADER)); +- mediatype->formattype = FORMAT_VideoInfo; +- mediatype->cbFormat = sizeof(VIDEOINFOHEADER); +- mediatype->pbFormat = (BYTE *)vi; +- +- vi->rcSource.bottom = vfd->size.h; +- vi->rcSource.right = vfd->size.w; +- vi->rcTarget.bottom = vfd->size.h; +- vi->rcTarget.right = vfd->size.w; +- +- vi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER); +- vi->bmiHeader.biPlanes = 1; +- vi->bmiHeader.biBitCount = vfi->bpp; +- vi->bmiHeader.biCompression = strm->param.fmt.id; +- } +- + if (!srcpin || !sinkpin || !mediatype) { + hr = VFW_E_TYPE_NOT_ACCEPTED; + goto on_error; +@@ -809,9 +720,6 @@ + } + video_info->bmiHeader.biSizeImage = DIBSIZE(video_info->bmiHeader); + mediatype->lSampleSize = DIBSIZE(video_info->bmiHeader); +- if (graph->csource_filter) +- SourceFilter_SetMediaType(graph->csource_filter, +- mediatype); + + hr = IFilterGraph_AddFilter(graph->filter_graph, + (IBaseFilter *)graph->rend_filter, +@@ -893,9 +801,9 @@ + pj_pool_t *pool; + struct dshow_stream *strm; + pj_status_t status; ++ const pjmedia_video_format_detail *vfd; + +- PJ_ASSERT_RETURN(param->dir == PJMEDIA_DIR_CAPTURE || +- param->dir == PJMEDIA_DIR_RENDER, PJ_EINVAL); ++ PJ_ASSERT_RETURN(param->dir == PJMEDIA_DIR_CAPTURE, PJ_EINVAL); + + if (!get_dshow_format_info(param->fmt.id)) + return PJMEDIA_EVID_BADFORMAT; +@@ -910,9 +818,6 @@ + pj_memcpy(&strm->vid_cb, cb, sizeof(*cb)); + strm->user_data = user_data; + +- if (param->dir & PJMEDIA_DIR_CAPTURE) { +- const pjmedia_video_format_detail *vfd; +- + /* Create capture stream here */ + status = create_filter_graph(PJMEDIA_DIR_CAPTURE, param->cap_id, + PJ_FALSE, PJ_FALSE, df, strm, +@@ -944,21 +849,6 @@ + + vfd = pjmedia_format_get_video_format_detail(¶m->fmt, PJ_TRUE); + strm->cap_ts_inc = PJMEDIA_SPF2(param->clock_rate, &vfd->fps, 1); +- } else if (param->dir & PJMEDIA_DIR_RENDER) { +- /* Create render stream here */ +- status = create_filter_graph(PJMEDIA_DIR_RENDER, param->rend_id, +- PJ_FALSE, PJ_FALSE, df, strm, +- &strm->dgraph); +- if (status != PJ_SUCCESS) +- goto on_error; +- } +- +- /* Apply the remaining settings */ +- if (param->flags & PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW) { +- dshow_stream_set_cap(&strm->base, +- PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW, +- ¶m->window); +- } + + /* Done */ + strm->base.op = &stream_op; +diff -ruN pjproject-2.10/pjmedia/src/pjmedia-videodev/dshow_filter.cpp pjsip/pjmedia/src/pjmedia-videodev/dshow_filter.cpp +--- pjproject-2.10/pjmedia/src/pjmedia-videodev/dshow_filter.cpp 1970-01-01 01:00:00.000000000 +0100 ++++ pjsip/pjmedia/src/pjmedia-videodev/dshow_filter.cpp 2021-02-06 18:47:09.030177557 +0100 +@@ -0,0 +1,83 @@ ++/* $Id: dshowclasses.cpp 4062 2012-04-19 06:36:57Z ming $ */ ++/* ++ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ++ */ ++ ++#include ++ ++ ++#if defined(PJMEDIA_VIDEO_DEV_HAS_DSHOW) && PJMEDIA_VIDEO_DEV_HAS_DSHOW != 0 ++ ++#include ++#include ++#include ++ ++typedef void (*input_callback)(void *user_data, IMediaSample *pMediaSample); ++ ++const GUID CLSID_NullRenderer = {0xF9168C5E, 0xCEB2, 0x4FAA, {0xB6, 0xBF, ++ 0x32, 0x9B, 0xF3, 0x9F, 0xA1, 0xE4}}; ++ ++class NullRenderer: public CBaseRenderer ++{ ++public: ++ NullRenderer(HRESULT *pHr); ++ virtual ~NullRenderer(); ++ ++ virtual HRESULT CheckMediaType(const CMediaType *pmt); ++ virtual HRESULT DoRenderSample(IMediaSample *pMediaSample); ++ ++ input_callback input_cb; ++ void *user_data; ++}; ++ ++NullRenderer::NullRenderer(HRESULT *pHr): CBaseRenderer(CLSID_NullRenderer, ++ "NullRenderer", ++ NULL, pHr) ++{ ++ input_cb = NULL; ++} ++ ++NullRenderer::~NullRenderer() ++{ ++} ++ ++HRESULT NullRenderer::CheckMediaType(const CMediaType *pmt) ++{ ++ return S_OK; ++} ++ ++HRESULT NullRenderer::DoRenderSample(IMediaSample *pMediaSample) ++{ ++ if (input_cb) ++ input_cb(user_data, pMediaSample); ++ ++ return S_OK; ++} ++ ++extern "C" IBaseFilter* NullRenderer_Create(input_callback input_cb, ++ void *user_data) ++{ ++ HRESULT hr; ++ NullRenderer *renderer = new NullRenderer(&hr); ++ renderer->AddRef(); ++ renderer->input_cb = input_cb; ++ renderer->user_data = user_data; ++ ++ return (CBaseFilter *)renderer; ++} ++ ++#endif /* PJMEDIA_VIDEO_DEV_HAS_DSHOW */ +diff -ruN pjproject-2.10/pjmedia/src/pjmedia-videodev/fb_dev.c pjsip/pjmedia/src/pjmedia-videodev/fb_dev.c +--- pjproject-2.10/pjmedia/src/pjmedia-videodev/fb_dev.c 1970-01-01 01:00:00.000000000 +0100 ++++ pjsip/pjmedia/src/pjmedia-videodev/fb_dev.c 2021-02-06 18:47:25.010632996 +0100 +@@ -0,0 +1,459 @@ ++/* ++ * Copyright (C) 2014-present AG Projects ++ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ++ */ ++#include ++#include ++#include ++#include ++ ++#if defined(PJMEDIA_HAS_VIDEO) && PJMEDIA_HAS_VIDEO != 0 && \ ++ defined(PJMEDIA_VIDEO_DEV_HAS_FB) && PJMEDIA_VIDEO_DEV_HAS_FB != 0 ++ ++#include ++ ++#define THIS_FILE "fb_dev.c" ++#define DEFAULT_CLOCK_RATE 90000 ++#define DEFAULT_WIDTH 640 ++#define DEFAULT_HEIGHT 480 ++#define DEFAULT_FPS 25 ++ ++ ++/* Supported formats */ ++#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0 ++static pjmedia_format_id fb_fmts[] = {PJMEDIA_FORMAT_ARGB}; ++#else ++static pjmedia_format_id fb_fmts[] = {PJMEDIA_FORMAT_BGRA}; ++#endif ++ ++ ++/* fb device info */ ++struct fb_dev_info ++{ ++ pjmedia_vid_dev_info info; ++}; ++ ++ ++/* factory */ ++struct fb_factory ++{ ++ pjmedia_vid_dev_factory base; ++ pj_pool_t *pool; ++ pj_pool_factory *pf; ++ ++ unsigned dev_count; ++ struct fb_dev_info *dev_info; ++}; ++ ++ ++/* Video stream. */ ++struct fb_stream ++{ ++ pjmedia_vid_dev_stream base; /**< Base stream */ ++ pjmedia_vid_dev_param param; /**< Settings */ ++ pj_pool_t *pool; /**< Memory pool. */ ++ ++ pjmedia_vid_dev_cb vid_cb; /**< Stream callback. */ ++ void *user_data; /**< Application data. */ ++ ++ struct fb_factory *ff; ++ pj_bool_t is_running; ++ pjmedia_rect_size vid_size; ++ ++ struct { ++ pjmedia_vid_dev_fb_frame_cb cb; ++ void *user_data; ++ } frame_handler; ++}; ++ ++ ++/* Prototypes */ ++static pj_status_t fb_factory_init(pjmedia_vid_dev_factory *f); ++static pj_status_t fb_factory_destroy(pjmedia_vid_dev_factory *f); ++static pj_status_t fb_factory_refresh(pjmedia_vid_dev_factory *f); ++static unsigned fb_factory_get_dev_count(pjmedia_vid_dev_factory *f); ++static pj_status_t fb_factory_get_dev_info(pjmedia_vid_dev_factory *f, ++ unsigned index, ++ pjmedia_vid_dev_info *info); ++static pj_status_t fb_factory_default_param(pj_pool_t *pool, ++ pjmedia_vid_dev_factory *f, ++ unsigned index, ++ pjmedia_vid_dev_param *param); ++static pj_status_t fb_factory_create_stream(pjmedia_vid_dev_factory *f, ++ pjmedia_vid_dev_param *param, ++ const pjmedia_vid_dev_cb *cb, ++ void *user_data, ++ pjmedia_vid_dev_stream **p_vid_strm); ++ ++static pj_status_t fb_stream_get_param(pjmedia_vid_dev_stream *strm, ++ pjmedia_vid_dev_param *param); ++static pj_status_t fb_stream_get_cap(pjmedia_vid_dev_stream *strm, ++ pjmedia_vid_dev_cap cap, ++ void *value); ++static pj_status_t fb_stream_set_cap(pjmedia_vid_dev_stream *strm, ++ pjmedia_vid_dev_cap cap, ++ const void *value); ++static pj_status_t fb_stream_put_frame(pjmedia_vid_dev_stream *strm, ++ const pjmedia_frame *frame); ++static pj_status_t fb_stream_start(pjmedia_vid_dev_stream *strm); ++static pj_status_t fb_stream_stop(pjmedia_vid_dev_stream *strm); ++static pj_status_t fb_stream_destroy(pjmedia_vid_dev_stream *strm); ++ ++ ++/* Operations */ ++static pjmedia_vid_dev_factory_op factory_op = ++{ ++ &fb_factory_init, ++ &fb_factory_destroy, ++ &fb_factory_get_dev_count, ++ &fb_factory_get_dev_info, ++ &fb_factory_default_param, ++ &fb_factory_create_stream, ++ &fb_factory_refresh ++}; ++ ++static pjmedia_vid_dev_stream_op stream_op = ++{ ++ &fb_stream_get_param, ++ &fb_stream_get_cap, ++ &fb_stream_set_cap, ++ &fb_stream_start, ++ NULL, ++ &fb_stream_put_frame, ++ &fb_stream_stop, ++ &fb_stream_destroy ++}; ++ ++ ++/**************************************************************************** ++ * Factory operations ++ */ ++/* ++ * Init FB video driver. ++ */ ++pjmedia_vid_dev_factory* pjmedia_fb_factory(pj_pool_factory *pf) ++{ ++ struct fb_factory *f; ++ pj_pool_t *pool; ++ ++ pool = pj_pool_create(pf, "fb video", 1000, 1000, NULL); ++ f = PJ_POOL_ZALLOC_T(pool, struct fb_factory); ++ f->pf = pf; ++ f->pool = pool; ++ f->base.op = &factory_op; ++ ++ return &f->base; ++} ++ ++ ++/* API: init factory */ ++static pj_status_t fb_factory_init(pjmedia_vid_dev_factory *f) ++{ ++ struct fb_factory *ff = (struct fb_factory*)f; ++ struct fb_dev_info *di; ++ unsigned i, l; ++ ++ /* Initialize input and output devices here */ ++ ff->dev_info = (struct fb_dev_info*) ++ pj_pool_calloc(ff->pool, 1, sizeof(struct fb_dev_info)); ++ ++ ff->dev_count = 0; ++ di = &ff->dev_info[ff->dev_count++]; ++ pj_bzero(di, sizeof(*di)); ++ strcpy(di->info.name, "FrameBuffer renderer"); ++ strcpy(di->info.driver, "FrameBuffer"); ++ di->info.dir = PJMEDIA_DIR_RENDER; ++ di->info.has_callback = PJ_FALSE; ++ di->info.caps = 0; ++ ++ for (i = 0; i < ff->dev_count; i++) { ++ di = &ff->dev_info[i]; ++ di->info.fmt_cnt = PJ_ARRAY_SIZE(fb_fmts); ++ di->info.caps |= PJMEDIA_VID_DEV_CAP_FORMAT; ++ ++ for (l = 0; l < PJ_ARRAY_SIZE(fb_fmts); l++) { ++ pjmedia_format *fmt = &di->info.fmt[l]; ++ pjmedia_format_init_video(fmt, ++ fb_fmts[l], ++ DEFAULT_WIDTH, ++ DEFAULT_HEIGHT, ++ DEFAULT_FPS, 1); ++ } ++ } ++ ++ PJ_LOG(4, (THIS_FILE, "FrameBuffer initialized")); ++ ++ return PJ_SUCCESS; ++} ++ ++ ++/* API: destroy factory */ ++static pj_status_t fb_factory_destroy(pjmedia_vid_dev_factory *f) ++{ ++ struct fb_factory *ff = (struct fb_factory*)f; ++ pj_pool_t *pool = ff->pool; ++ ++ ff->pool = NULL; ++ pj_pool_release(pool); ++ ++ return PJ_SUCCESS; ++} ++ ++ ++/* API: refresh the list of devices */ ++static pj_status_t fb_factory_refresh(pjmedia_vid_dev_factory *f) ++{ ++ PJ_UNUSED_ARG(f); ++ return PJ_SUCCESS; ++} ++ ++ ++/* API: get number of devices */ ++static unsigned fb_factory_get_dev_count(pjmedia_vid_dev_factory *f) ++{ ++ struct fb_factory *ff = (struct fb_factory*)f; ++ return ff->dev_count; ++} ++ ++ ++/* API: get device info */ ++static pj_status_t fb_factory_get_dev_info(pjmedia_vid_dev_factory *f, ++ unsigned index, ++ pjmedia_vid_dev_info *info) ++{ ++ struct fb_factory *ff = (struct fb_factory*)f; ++ ++ PJ_ASSERT_RETURN(index < ff->dev_count, PJMEDIA_EVID_INVDEV); ++ pj_memcpy(info, &ff->dev_info[index].info, sizeof(*info)); ++ ++ return PJ_SUCCESS; ++} ++ ++ ++/* API: create default device parameter */ ++static pj_status_t fb_factory_default_param(pj_pool_t *pool, ++ pjmedia_vid_dev_factory *f, ++ unsigned index, ++ pjmedia_vid_dev_param *param) ++{ ++ struct fb_factory *ff = (struct fb_factory*)f; ++ struct fb_dev_info *di = &ff->dev_info[index]; ++ ++ PJ_ASSERT_RETURN(index < ff->dev_count, PJMEDIA_EVID_INVDEV); ++ PJ_UNUSED_ARG(pool); ++ ++ pj_bzero(param, sizeof(*param)); ++ param->dir = PJMEDIA_DIR_RENDER; ++ param->rend_id = index; ++ param->cap_id = PJMEDIA_VID_INVALID_DEV; ++ ++ /* Set the device capabilities here */ ++ param->flags = PJMEDIA_VID_DEV_CAP_FORMAT; ++ param->clock_rate = DEFAULT_CLOCK_RATE; ++ pj_memcpy(¶m->fmt, &di->info.fmt[0], sizeof(param->fmt)); ++ ++ return PJ_SUCCESS; ++} ++ ++ ++/* API: Put frame from stream */ ++static pj_status_t fb_stream_put_frame(pjmedia_vid_dev_stream *strm, ++ const pjmedia_frame *frame) ++{ ++ struct fb_stream *stream = (struct fb_stream*)strm; ++ ++ if (!stream->is_running) ++ return PJ_EINVALIDOP; ++ ++ if (frame->size==0 || frame->buf==NULL) ++ return PJ_SUCCESS; ++ ++ if (stream->frame_handler.cb) ++ stream->frame_handler.cb(frame, stream->vid_size, stream->frame_handler.user_data); ++ ++ return PJ_SUCCESS; ++} ++ ++/* API: create stream */ ++static pj_status_t fb_factory_create_stream(pjmedia_vid_dev_factory *f, ++ pjmedia_vid_dev_param *param, ++ const pjmedia_vid_dev_cb *cb, ++ void *user_data, ++ pjmedia_vid_dev_stream **p_vid_strm) ++{ ++ struct fb_factory *ff = (struct fb_factory*)f; ++ pj_pool_t *pool; ++ pj_status_t status; ++ struct fb_stream *strm; ++ const pjmedia_video_format_info *vfi; ++ ++ PJ_ASSERT_RETURN(f && param && p_vid_strm, PJ_EINVAL); ++ PJ_ASSERT_RETURN(param->dir == PJMEDIA_DIR_RENDER, PJ_EINVAL); ++ PJ_ASSERT_RETURN(param->fmt.type == PJMEDIA_TYPE_VIDEO && ++ param->fmt.detail_type == PJMEDIA_FORMAT_DETAIL_VIDEO && ++ param->dir == PJMEDIA_DIR_RENDER, ++ PJ_EINVAL); ++ ++ vfi = pjmedia_get_video_format_info(NULL, param->fmt.id); ++ if (!vfi) ++ return PJMEDIA_EVID_BADFORMAT; ++ ++ /* Create and Initialize stream descriptor */ ++ pool = pj_pool_create(ff->pf, "fb-dev", 1000, 1000, NULL); ++ PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); ++ ++ strm = PJ_POOL_ZALLOC_T(pool, struct fb_stream); ++ pj_memcpy(&strm->param, param, sizeof(*param)); ++ strm->pool = pool; ++ strm->ff = ff; ++ pj_memcpy(&strm->vid_cb, cb, sizeof(*cb)); ++ strm->user_data = user_data; ++ ++ status = fb_stream_set_cap(&strm->base, PJMEDIA_VID_DEV_CAP_FORMAT, ¶m->fmt); ++ if (status != PJ_SUCCESS) { ++ fb_stream_destroy((pjmedia_vid_dev_stream *)strm); ++ return status; ++ } ++ ++ /* Done */ ++ strm->base.op = &stream_op; ++ *p_vid_strm = &strm->base; ++ ++ return PJ_SUCCESS; ++} ++ ++ ++/* API: Get stream info. */ ++static pj_status_t fb_stream_get_param(pjmedia_vid_dev_stream *s, ++ pjmedia_vid_dev_param *pi) ++{ ++ struct fb_stream *strm = (struct fb_stream*)s; ++ PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL); ++ ++ pj_memcpy(pi, &strm->param, sizeof(*pi)); ++ ++ return PJ_SUCCESS; ++} ++ ++ ++/* API: get capability */ ++static pj_status_t fb_stream_get_cap(pjmedia_vid_dev_stream *s, ++ pjmedia_vid_dev_cap cap, ++ void *pval) ++{ ++ struct fb_stream *strm = (struct fb_stream*)s; ++ ++ PJ_UNUSED_ARG(strm); ++ PJ_UNUSED_ARG(cap); ++ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); ++ ++ return PJMEDIA_EVID_INVCAP; ++} ++ ++ ++/* API: set capability */ ++static pj_status_t fb_stream_set_cap(pjmedia_vid_dev_stream *s, ++ pjmedia_vid_dev_cap cap, ++ const void *pval) ++{ ++ struct fb_stream *strm = (struct fb_stream*)s; ++ ++ PJ_UNUSED_ARG(strm); ++ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); ++ ++ if (cap == PJMEDIA_VID_DEV_CAP_FORMAT) { ++ const pjmedia_video_format_info *vfi; ++ pjmedia_video_format_detail *vfd; ++ pjmedia_format *fmt = (pjmedia_format *)pval; ++ ++ vfi = pjmedia_get_video_format_info(pjmedia_video_format_mgr_instance(), fmt->id); ++ if (!vfi) ++ return PJMEDIA_EVID_BADFORMAT; ++ ++ pjmedia_format_copy(&strm->param.fmt, fmt); ++ ++ vfd = pjmedia_format_get_video_format_detail(fmt, PJ_TRUE); ++ pj_memcpy(&strm->vid_size, &vfd->size, sizeof(vfd->size)); ++ if (strm->param.disp_size.w == 0 || strm->param.disp_size.h == 0) ++ pj_memcpy(&strm->param.disp_size, &vfd->size, sizeof(vfd->size)); ++ ++ return PJ_SUCCESS; ++ } ++ ++ return PJMEDIA_EVID_INVCAP; ++} ++ ++ ++/* API: Start stream. */ ++static pj_status_t fb_stream_start(pjmedia_vid_dev_stream *strm) ++{ ++ struct fb_stream *stream = (struct fb_stream*)strm; ++ PJ_UNUSED_ARG(strm); ++ ++ PJ_LOG(4, (THIS_FILE, "Starting FB video stream")); ++ stream->is_running = PJ_TRUE; ++ ++ return PJ_SUCCESS; ++} ++ ++ ++/* API: Stop stream. */ ++static pj_status_t fb_stream_stop(pjmedia_vid_dev_stream *strm) ++{ ++ struct fb_stream *stream = (struct fb_stream*)strm; ++ PJ_UNUSED_ARG(strm); ++ ++ PJ_LOG(4, (THIS_FILE, "Stopping FB video stream")); ++ stream->is_running = PJ_FALSE; ++ ++ return PJ_SUCCESS; ++} ++ ++ ++/* API: Destroy stream. */ ++static pj_status_t fb_stream_destroy(pjmedia_vid_dev_stream *strm) ++{ ++ struct fb_stream *stream = (struct fb_stream*)strm; ++ ++ PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); ++ ++ fb_stream_stop(strm); ++ pj_pool_release(stream->pool); ++ ++ return PJ_SUCCESS; ++} ++ ++ ++/* API: set callback for handling frames */ ++pj_status_t ++pjmedia_vid_dev_fb_set_callback(pjmedia_vid_dev_stream *strm, ++ pjmedia_vid_dev_fb_frame_cb cb, ++ void *user_data) ++{ ++ struct fb_stream *stream = (struct fb_stream*)strm; ++ ++ PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); ++ if (stream->is_running) ++ return PJ_EBUSY; ++ ++ stream->frame_handler.cb = cb; ++ stream->frame_handler.user_data = user_data; ++ ++ return PJ_SUCCESS; ++} ++ ++#endif /* PJMEDIA_VIDEO_DEV_HAS_FB */ +diff -ruN pjproject-2.10/pjmedia/src/pjmedia-videodev/null_dev.c pjsip/pjmedia/src/pjmedia-videodev/null_dev.c +--- pjproject-2.10/pjmedia/src/pjmedia-videodev/null_dev.c 1970-01-01 01:00:00.000000000 +0100 ++++ pjsip/pjmedia/src/pjmedia-videodev/null_dev.c 2021-02-06 18:47:47.343269399 +0100 +@@ -0,0 +1,440 @@ ++/* $Id: colorbar_dev.c 4158 2012-06-06 09:56:14Z nanang $ */ ++/* ++ * Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com) ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ++ */ ++#include ++#include ++#include ++#include ++ ++ ++#if defined(PJMEDIA_HAS_VIDEO) && PJMEDIA_HAS_VIDEO != 0 && \ ++ defined(PJMEDIA_VIDEO_DEV_HAS_NULL) && \ ++ PJMEDIA_VIDEO_DEV_HAS_NULL != 0 ++ ++ ++#define THIS_FILE "null_dev.c" ++#define DEFAULT_CLOCK_RATE 90000 ++#define DEFAULT_WIDTH 640 ++#define DEFAULT_HEIGHT 480 ++#define DEFAULT_FPS 5 ++ ++/* null_ device info */ ++struct null_dev_info ++{ ++ pjmedia_vid_dev_info info; ++}; ++ ++/* null_ factory */ ++struct null_factory ++{ ++ pjmedia_vid_dev_factory base; ++ pj_pool_t *pool; ++ pj_pool_factory *pf; ++ ++ unsigned dev_count; ++ struct null_dev_info *dev_info; ++}; ++ ++struct null_fmt_info { ++ pjmedia_format_id fmt_id; /* Format ID */ ++}; ++ ++/* Null video source supports */ ++static struct null_fmt_info null_fmts[] = ++{ ++ { PJMEDIA_FORMAT_BGRA }, ++}; ++ ++/* Video stream. */ ++struct null_stream ++{ ++ pjmedia_vid_dev_stream base; /**< Base stream */ ++ pjmedia_vid_dev_param param; /**< Settings */ ++ pj_pool_t *pool; /**< Memory pool. */ ++ ++ pjmedia_vid_dev_cb vid_cb; /**< Stream callback. */ ++ void *user_data; /**< Application data. */ ++ ++ const struct null_fmt_info *cbfi; ++ const pjmedia_video_format_info *vfi; ++ pjmedia_video_apply_fmt_param vafp; ++ pj_uint8_t *first_line[PJMEDIA_MAX_VIDEO_PLANES]; ++ pj_timestamp ts; ++ unsigned ts_inc; ++}; ++ ++ ++/* Prototypes */ ++static pj_status_t null_factory_init(pjmedia_vid_dev_factory *f); ++static pj_status_t null_factory_destroy(pjmedia_vid_dev_factory *f); ++static pj_status_t null_factory_refresh(pjmedia_vid_dev_factory *f); ++static unsigned null_factory_get_dev_count(pjmedia_vid_dev_factory *f); ++static pj_status_t null_factory_get_dev_info(pjmedia_vid_dev_factory *f, ++ unsigned index, ++ pjmedia_vid_dev_info *info); ++static pj_status_t null_factory_default_param(pj_pool_t *pool, ++ pjmedia_vid_dev_factory *f, ++ unsigned index, ++ pjmedia_vid_dev_param *param); ++static pj_status_t null_factory_create_stream( ++ pjmedia_vid_dev_factory *f, ++ pjmedia_vid_dev_param *param, ++ const pjmedia_vid_dev_cb *cb, ++ void *user_data, ++ pjmedia_vid_dev_stream **p_vid_strm); ++ ++static pj_status_t null_stream_get_param(pjmedia_vid_dev_stream *strm, ++ pjmedia_vid_dev_param *param); ++static pj_status_t null_stream_get_cap(pjmedia_vid_dev_stream *strm, ++ pjmedia_vid_dev_cap cap, ++ void *value); ++static pj_status_t null_stream_set_cap(pjmedia_vid_dev_stream *strm, ++ pjmedia_vid_dev_cap cap, ++ const void *value); ++static pj_status_t null_stream_get_frame(pjmedia_vid_dev_stream *strm, ++ pjmedia_frame *frame); ++static pj_status_t null_stream_start(pjmedia_vid_dev_stream *strm); ++static pj_status_t null_stream_stop(pjmedia_vid_dev_stream *strm); ++static pj_status_t null_stream_destroy(pjmedia_vid_dev_stream *strm); ++ ++/* Operations */ ++static pjmedia_vid_dev_factory_op factory_op = ++{ ++ &null_factory_init, ++ &null_factory_destroy, ++ &null_factory_get_dev_count, ++ &null_factory_get_dev_info, ++ &null_factory_default_param, ++ &null_factory_create_stream, ++ &null_factory_refresh ++}; ++ ++static pjmedia_vid_dev_stream_op stream_op = ++{ ++ &null_stream_get_param, ++ &null_stream_get_cap, ++ &null_stream_set_cap, ++ &null_stream_start, ++ &null_stream_get_frame, ++ NULL, ++ &null_stream_stop, ++ &null_stream_destroy ++}; ++ ++ ++/**************************************************************************** ++ * Factory operations ++ */ ++/* ++ * Init null_ video driver. ++ */ ++pjmedia_vid_dev_factory* pjmedia_null_factory(pj_pool_factory *pf) ++{ ++ struct null_factory *f; ++ pj_pool_t *pool; ++ ++ pool = pj_pool_create(pf, "null video", 512, 512, NULL); ++ f = PJ_POOL_ZALLOC_T(pool, struct null_factory); ++ f->pf = pf; ++ f->pool = pool; ++ f->base.op = &factory_op; ++ ++ return &f->base; ++} ++ ++ ++/* API: init factory */ ++static pj_status_t null_factory_init(pjmedia_vid_dev_factory *f) ++{ ++ struct null_factory *cf = (struct null_factory*)f; ++ struct null_dev_info *ddi; ++ unsigned i; ++ ++ cf->dev_count = 1; ++ cf->dev_info = (struct null_dev_info*) ++ pj_pool_calloc(cf->pool, cf->dev_count, ++ sizeof(struct null_dev_info)); ++ ++ ddi = &cf->dev_info[0]; ++ pj_bzero(ddi, sizeof(*ddi)); ++ pj_ansi_strncpy(ddi->info.name, "Null video device", ++ sizeof(ddi->info.name)); ++ ddi->info.driver[sizeof(ddi->info.driver)-1] = '\0'; ++ pj_ansi_strncpy(ddi->info.driver, "Null", sizeof(ddi->info.driver)); ++ ddi->info.driver[sizeof(ddi->info.driver)-1] = '\0'; ++ ddi->info.dir = PJMEDIA_DIR_CAPTURE; ++ ddi->info.has_callback = PJ_FALSE; ++ ++ ddi->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT; ++ ddi->info.fmt_cnt = sizeof(null_fmts)/sizeof(null_fmts[0]); ++ for (i = 0; i < ddi->info.fmt_cnt; i++) { ++ pjmedia_format *fmt = &ddi->info.fmt[i]; ++ pjmedia_format_init_video(fmt, null_fmts[i].fmt_id, ++ DEFAULT_WIDTH, DEFAULT_HEIGHT, ++ DEFAULT_FPS, 1); ++ } ++ ++ PJ_LOG(4, (THIS_FILE, "Null video src initialized with %d device(s):", cf->dev_count)); ++ for (i = 0; i < cf->dev_count; i++) { ++ PJ_LOG(4, (THIS_FILE, "%2d: %s", i, cf->dev_info[i].info.name)); ++ } ++ ++ return PJ_SUCCESS; ++} ++ ++/* API: destroy factory */ ++static pj_status_t null_factory_destroy(pjmedia_vid_dev_factory *f) ++{ ++ struct null_factory *cf = (struct null_factory*)f; ++ pj_pool_t *pool = cf->pool; ++ ++ cf->pool = NULL; ++ pj_pool_release(pool); ++ ++ return PJ_SUCCESS; ++} ++ ++/* API: refresh the list of devices */ ++static pj_status_t null_factory_refresh(pjmedia_vid_dev_factory *f) ++{ ++ PJ_UNUSED_ARG(f); ++ return PJ_SUCCESS; ++} ++ ++/* API: get number of devices */ ++static unsigned null_factory_get_dev_count(pjmedia_vid_dev_factory *f) ++{ ++ struct null_factory *cf = (struct null_factory*)f; ++ return cf->dev_count; ++} ++ ++/* API: get device info */ ++static pj_status_t null_factory_get_dev_info(pjmedia_vid_dev_factory *f, ++ unsigned index, ++ pjmedia_vid_dev_info *info) ++{ ++ struct null_factory *cf = (struct null_factory*)f; ++ ++ PJ_ASSERT_RETURN(index < cf->dev_count, PJMEDIA_EVID_INVDEV); ++ ++ pj_memcpy(info, &cf->dev_info[index].info, sizeof(*info)); ++ ++ return PJ_SUCCESS; ++} ++ ++/* API: create default device parameter */ ++static pj_status_t null_factory_default_param(pj_pool_t *pool, ++ pjmedia_vid_dev_factory *f, ++ unsigned index, ++ pjmedia_vid_dev_param *param) ++{ ++ struct null_factory *cf = (struct null_factory*)f; ++ struct null_dev_info *di = &cf->dev_info[index]; ++ ++ PJ_ASSERT_RETURN(index < cf->dev_count, PJMEDIA_EVID_INVDEV); ++ ++ PJ_UNUSED_ARG(pool); ++ ++ pj_bzero(param, sizeof(*param)); ++ param->dir = PJMEDIA_DIR_CAPTURE; ++ param->cap_id = index; ++ param->rend_id = PJMEDIA_VID_INVALID_DEV; ++ param->flags = PJMEDIA_VID_DEV_CAP_FORMAT; ++ param->clock_rate = DEFAULT_CLOCK_RATE; ++ pj_memcpy(¶m->fmt, &di->info.fmt[0], sizeof(param->fmt)); ++ ++ return PJ_SUCCESS; ++} ++ ++static const struct null_fmt_info* get_null_fmt_info(pjmedia_format_id id) ++{ ++ unsigned i; ++ ++ for (i = 0; i < sizeof(null_fmts)/sizeof(null_fmts[0]); i++) { ++ if (null_fmts[i].fmt_id == id) ++ return &null_fmts[i]; ++ } ++ ++ return NULL; ++} ++ ++ ++/* API: create stream */ ++static pj_status_t null_factory_create_stream( ++ pjmedia_vid_dev_factory *f, ++ pjmedia_vid_dev_param *param, ++ const pjmedia_vid_dev_cb *cb, ++ void *user_data, ++ pjmedia_vid_dev_stream **p_vid_strm) ++{ ++ struct null_factory *cf = (struct null_factory*)f; ++ pj_pool_t *pool; ++ struct null_stream *strm; ++ const pjmedia_video_format_detail *vfd; ++ const pjmedia_video_format_info *vfi; ++ pjmedia_video_apply_fmt_param vafp; ++ const struct null_fmt_info *cbfi; ++ unsigned i; ++ ++ PJ_ASSERT_RETURN(f && param && p_vid_strm, PJ_EINVAL); ++ PJ_ASSERT_RETURN(param->fmt.type == PJMEDIA_TYPE_VIDEO && ++ param->fmt.detail_type == PJMEDIA_FORMAT_DETAIL_VIDEO && ++ param->dir == PJMEDIA_DIR_CAPTURE, ++ PJ_EINVAL); ++ ++ pj_bzero(&vafp, sizeof(vafp)); ++ ++ vfd = pjmedia_format_get_video_format_detail(¶m->fmt, PJ_TRUE); ++ vfi = pjmedia_get_video_format_info(NULL, param->fmt.id); ++ cbfi = get_null_fmt_info(param->fmt.id); ++ if (!vfi || !cbfi) ++ return PJMEDIA_EVID_BADFORMAT; ++ ++ vafp.size = param->fmt.det.vid.size; ++ if (vfi->apply_fmt(vfi, &vafp) != PJ_SUCCESS) ++ return PJMEDIA_EVID_BADFORMAT; ++ ++ /* Create and Initialize stream descriptor */ ++ pool = pj_pool_create(cf->pf, "null-dev", 512, 512, NULL); ++ PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); ++ ++ strm = PJ_POOL_ZALLOC_T(pool, struct null_stream); ++ pj_memcpy(&strm->param, param, sizeof(*param)); ++ strm->pool = pool; ++ pj_memcpy(&strm->vid_cb, cb, sizeof(*cb)); ++ strm->user_data = user_data; ++ strm->vfi = vfi; ++ strm->cbfi = cbfi; ++ pj_memcpy(&strm->vafp, &vafp, sizeof(vafp)); ++ strm->ts_inc = PJMEDIA_SPF2(param->clock_rate, &vfd->fps, 1); ++ ++ for (i = 0; i < vfi->plane_cnt; ++i) { ++ strm->first_line[i] = pj_pool_alloc(pool, vafp.strides[i]); ++ pj_memset(strm->first_line[i], 0, vafp.strides[i]); ++ } ++ ++ /* Done */ ++ strm->base.op = &stream_op; ++ *p_vid_strm = &strm->base; ++ ++ return PJ_SUCCESS; ++} ++ ++/* API: Get stream info. */ ++static pj_status_t null_stream_get_param(pjmedia_vid_dev_stream *s, ++ pjmedia_vid_dev_param *pi) ++{ ++ struct null_stream *strm = (struct null_stream*)s; ++ ++ PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL); ++ ++ pj_memcpy(pi, &strm->param, sizeof(*pi)); ++ return PJ_SUCCESS; ++} ++ ++/* API: get capability */ ++static pj_status_t null_stream_get_cap(pjmedia_vid_dev_stream *s, ++ pjmedia_vid_dev_cap cap, ++ void *pval) ++{ ++ struct null_stream *strm = (struct null_stream*)s; ++ ++ PJ_UNUSED_ARG(strm); ++ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); ++ return PJMEDIA_EVID_INVCAP; ++} ++ ++/* API: set capability */ ++static pj_status_t null_stream_set_cap(pjmedia_vid_dev_stream *s, ++ pjmedia_vid_dev_cap cap, ++ const void *pval) ++{ ++ struct null_stream *strm = (struct null_stream*)s; ++ ++ PJ_UNUSED_ARG(strm); ++ PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); ++ return PJMEDIA_EVID_INVCAP; ++} ++ ++ ++/* API: Get frame from stream */ ++static pj_status_t null_stream_get_frame(pjmedia_vid_dev_stream *strm, ++ pjmedia_frame *frame) ++{ ++ struct null_stream *stream = (struct null_stream*)strm; ++ unsigned i; ++ pj_uint8_t *ptr = frame->buf; ++ ++ frame->type = PJMEDIA_FRAME_TYPE_VIDEO; ++ frame->bit_info = 0; ++ frame->timestamp = stream->ts; ++ stream->ts.u64 += stream->ts_inc; ++ ++ /* paint subsequent lines */ ++ for (i=0; ivfi->plane_cnt; ++i) { ++ pj_uint8_t *plane_end; ++ plane_end = ptr + stream->vafp.plane_bytes[i]; ++ while (ptr < plane_end) { ++ pj_memcpy(ptr, stream->first_line[i], stream->vafp.strides[i]); ++ ptr += stream->vafp.strides[i]; ++ } ++ } ++ ++ return PJ_SUCCESS; ++} ++ ++/* API: Start stream. */ ++static pj_status_t null_stream_start(pjmedia_vid_dev_stream *strm) ++{ ++ struct null_stream *stream = (struct null_stream*)strm; ++ ++ PJ_UNUSED_ARG(stream); ++ ++ PJ_LOG(4, (THIS_FILE, "Starting null video stream")); ++ ++ return PJ_SUCCESS; ++} ++ ++/* API: Stop stream. */ ++static pj_status_t null_stream_stop(pjmedia_vid_dev_stream *strm) ++{ ++ struct null_stream *stream = (struct null_stream*)strm; ++ ++ PJ_UNUSED_ARG(stream); ++ ++ PJ_LOG(4, (THIS_FILE, "Stopping null video stream")); ++ ++ return PJ_SUCCESS; ++} ++ ++ ++/* API: Destroy stream. */ ++static pj_status_t null_stream_destroy(pjmedia_vid_dev_stream *strm) ++{ ++ struct null_stream *stream = (struct null_stream*)strm; ++ ++ PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); ++ ++ null_stream_stop(strm); ++ ++ pj_pool_release(stream->pool); ++ ++ return PJ_SUCCESS; ++} ++ ++#endif /* PJMEDIA_VIDEO_DEV_HAS_NULL */ +diff -ruN pjproject-2.10/pjmedia/src/pjmedia-videodev/videodev.c pjsip/pjmedia/src/pjmedia-videodev/videodev.c +--- pjproject-2.10/pjmedia/src/pjmedia-videodev/videodev.c 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/pjmedia/src/pjmedia-videodev/videodev.c 2021-02-06 23:01:33.883264555 +0100 +@@ -51,6 +51,10 @@ + pjmedia_vid_dev_factory* pjmedia_qt_factory(pj_pool_factory *pf); + #endif + ++#if PJMEDIA_VIDEO_DEV_HAS_AVF ++pjmedia_vid_dev_factory* pjmedia_avf_factory(pj_pool_factory *pf); ++#endif ++ + #if PJMEDIA_VIDEO_DEV_HAS_DARWIN + pjmedia_vid_dev_factory* pjmedia_darwin_factory(pj_pool_factory *pf); + #endif +@@ -59,6 +63,14 @@ + pjmedia_vid_dev_factory* pjmedia_opengl_factory(pj_pool_factory *pf); + #endif + ++#if PJMEDIA_VIDEO_DEV_HAS_FB ++pjmedia_vid_dev_factory* pjmedia_fb_factory(pj_pool_factory *pf); ++#endif ++ ++#if PJMEDIA_VIDEO_DEV_HAS_NULL ++pjmedia_vid_dev_factory* pjmedia_null_factory(pj_pool_factory *pf); ++#endif ++ + #if PJMEDIA_VIDEO_DEV_HAS_ANDROID + pjmedia_vid_dev_factory* pjmedia_and_factory(pj_pool_factory *pf); + #endif +@@ -98,6 +110,9 @@ + #if PJMEDIA_VIDEO_DEV_HAS_QT + vid_subsys->drv[vid_subsys->drv_cnt++].create = &pjmedia_qt_factory; + #endif ++#if PJMEDIA_VIDEO_DEV_HAS_AVF ++ vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_avf_factory; ++#endif + #if PJMEDIA_VIDEO_DEV_HAS_OPENGL + vid_subsys->drv[vid_subsys->drv_cnt++].create = &pjmedia_opengl_factory; + #endif +@@ -122,6 +137,12 @@ + */ + vid_subsys->drv[vid_subsys->drv_cnt++].create = &pjmedia_cbar_factory; + #endif ++#if PJMEDIA_VIDEO_DEV_HAS_FB ++ vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_fb_factory; ++#endif ++#if PJMEDIA_VIDEO_DEV_HAS_NULL ++ vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_null_factory; ++#endif + + /* Initialize each factory and build the device ID list */ + for (i=0; idrv_cnt; ++i) { +diff -ruN pjproject-2.10/pjnath/include/pjnath/ice_strans.h pjsip/pjnath/include/pjnath/ice_strans.h +--- pjproject-2.10/pjnath/include/pjnath/ice_strans.h 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/pjnath/include/pjnath/ice_strans.h 2021-02-06 17:37:27.238934521 +0100 +@@ -144,6 +144,52 @@ + + } pj_ice_strans_op; + ++ ++/** ++ * ICE stream transport's state. ++ */ ++typedef enum pj_ice_strans_state ++{ ++ /** ++ * ICE stream transport is not created. ++ */ ++ PJ_ICE_STRANS_STATE_NULL, ++ ++ /** ++ * ICE candidate gathering process is in progress. ++ */ ++ PJ_ICE_STRANS_STATE_INIT, ++ ++ /** ++ * ICE stream transport initialization/candidate gathering process is ++ * complete, ICE session may be created on this stream transport. ++ */ ++ PJ_ICE_STRANS_STATE_READY, ++ ++ /** ++ * New session has been created and the session is ready. ++ */ ++ PJ_ICE_STRANS_STATE_SESS_READY, ++ ++ /** ++ * ICE negotiation is in progress. ++ */ ++ PJ_ICE_STRANS_STATE_NEGO, ++ ++ /** ++ * ICE negotiation has completed successfully and media is ready ++ * to be used. ++ */ ++ PJ_ICE_STRANS_STATE_RUNNING, ++ ++ /** ++ * ICE negotiation has completed with failure. ++ */ ++ PJ_ICE_STRANS_STATE_FAILED ++ ++} pj_ice_strans_state; ++ ++ + /** + * This structure contains callbacks that will be called by the + * ICE stream transport. +@@ -192,6 +238,18 @@ + pj_ice_strans_op op, + pj_status_t status); + ++ /** ++ * Callback to report ICE state changes. ++ * ++ * @param ice_st The ICE stream transport. ++ * @param prev Previous state. ++ * @param curr Current state. ++ */ ++ void (*on_ice_state)(pj_ice_strans *ice_st, ++ pj_ice_strans_state prev, ++ pj_ice_strans_state curr); ++ ++ + } pj_ice_strans_cb; + + +@@ -522,51 +580,6 @@ + } pj_ice_strans_cfg; + + +-/** +- * ICE stream transport's state. +- */ +-typedef enum pj_ice_strans_state +-{ +- /** +- * ICE stream transport is not created. +- */ +- PJ_ICE_STRANS_STATE_NULL, +- +- /** +- * ICE candidate gathering process is in progress. +- */ +- PJ_ICE_STRANS_STATE_INIT, +- +- /** +- * ICE stream transport initialization/candidate gathering process is +- * complete, ICE session may be created on this stream transport. +- */ +- PJ_ICE_STRANS_STATE_READY, +- +- /** +- * New session has been created and the session is ready. +- */ +- PJ_ICE_STRANS_STATE_SESS_READY, +- +- /** +- * ICE negotiation is in progress. +- */ +- PJ_ICE_STRANS_STATE_NEGO, +- +- /** +- * ICE negotiation has completed successfully and media is ready +- * to be used. +- */ +- PJ_ICE_STRANS_STATE_RUNNING, +- +- /** +- * ICE negotiation has completed with failure. +- */ +- PJ_ICE_STRANS_STATE_FAILED +- +-} pj_ice_strans_state; +- +- + /** + * Initialize ICE transport configuration with default values. + * +@@ -921,6 +934,27 @@ + unsigned comp_id); + + /** ++ * Retrieve the ICE session associated with this transport ++ * ++ * @param ice_st The ICE stream transport. ++ * ++ * @return The ICE session associated with this transport ++ */ ++PJ_DECL(pj_ice_sess*) ++pj_ice_strans_get_session(const pj_ice_strans *ice_st); ++ ++/** ++ * Retrieve the ICE start time ++ * ++ * @param ice_st The ICE stream transport. ++ * ++ * @return The ICE start time ++ */ ++PJ_DECL(pj_time_val) ++pj_ice_strans_get_start_time(const pj_ice_strans *ice_st); ++ ++ ++/** + * Stop and destroy the ICE session inside this media transport. Application + * needs to call this function once the media session is over (the call has + * been disconnected). +diff -ruN pjproject-2.10/pjnath/src/pjnath/ice_strans.c pjsip/pjnath/src/pjnath/ice_strans.c +--- pjproject-2.10/pjnath/src/pjnath/ice_strans.c 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/pjnath/src/pjnath/ice_strans.c 2021-02-06 17:27:03.341194358 +0100 +@@ -240,6 +240,19 @@ + } sock_user_data; + + ++/* Set ICE state*/ ++static void set_ice_state(pj_ice_strans *ice_st, pj_ice_strans_state state) ++{ ++ pj_ice_strans_state prev = ice_st->state; ++ ++ if (prev != state) { ++ ice_st->state = state; ++ if (ice_st->cb.on_ice_state) ++ (*ice_st->cb.on_ice_state)(ice_st, prev, state); ++ } ++} ++ ++ + /* Validate configuration */ + static pj_status_t pj_ice_strans_cfg_check_valid(const pj_ice_strans_cfg *cfg) + { +@@ -928,7 +941,7 @@ + pj_pool_calloc(pool, comp_cnt, sizeof(pj_ice_strans_comp*)); + + /* Move state to candidate gathering */ +- ice_st->state = PJ_ICE_STRANS_STATE_INIT; ++ set_ice_state(ice_st, PJ_ICE_STRANS_STATE_INIT); + + /* Acquire initialization mutex to prevent callback to be + * called before we finish initialization. +@@ -1123,7 +1136,7 @@ + * candidate for a component. + */ + ice_st->cb_called = PJ_TRUE; +- ice_st->state = PJ_ICE_STRANS_STATE_READY; ++ set_ice_state(ice_st, PJ_ICE_STRANS_STATE_READY); + if (ice_st->cb.on_ice_complete) + (*ice_st->cb.on_ice_complete)(ice_st, PJ_ICE_STRANS_OP_INIT, + status); +@@ -1289,7 +1302,7 @@ + } + + /* ICE session is ready for negotiation */ +- ice_st->state = PJ_ICE_STRANS_STATE_SESS_READY; ++ set_ice_state(ice_st, PJ_ICE_STRANS_STATE_SESS_READY); + + return PJ_SUCCESS; + +@@ -1518,7 +1531,7 @@ + return status; + } + +- ice_st->state = PJ_ICE_STRANS_STATE_NEGO; ++ set_ice_state(ice_st, PJ_ICE_STRANS_STATE_NEGO); + return status; + } + +@@ -1539,6 +1552,25 @@ + } + + /* ++ * Get ICE session. ++ */ ++PJ_DEF(pj_ice_sess*) ++pj_ice_strans_get_session(const pj_ice_strans *ice_st) ++{ ++ return ice_st->ice; ++} ++ ++/* ++ * Get ICE start time. ++ */ ++PJ_DEF(pj_time_val) ++pj_ice_strans_get_start_time(const pj_ice_strans *ice_st) ++{ ++ ++ return ice_st->start_time; ++} ++ ++/* + * Stop ICE! + */ + PJ_DEF(pj_status_t) pj_ice_strans_stop_ice(pj_ice_strans *ice_st) +@@ -1556,7 +1588,7 @@ + ice_st->ice = NULL; + } + +- ice_st->state = PJ_ICE_STRANS_STATE_INIT; ++ set_ice_state(ice_st, PJ_ICE_STRANS_STATE_INIT); + + pj_grp_lock_release(ice_st->grp_lock); + +@@ -1877,8 +1909,8 @@ + } + } + +- ice_st->state = (status==PJ_SUCCESS) ? PJ_ICE_STRANS_STATE_RUNNING : +- PJ_ICE_STRANS_STATE_FAILED; ++ set_ice_state(ice_st, (status==PJ_SUCCESS) ? PJ_ICE_STRANS_STATE_RUNNING : ++ PJ_ICE_STRANS_STATE_FAILED); + + pj_log_push_indent(); + (*cb.on_ice_complete)(ice_st, PJ_ICE_STRANS_OP_NEGOTIATION, status); +diff -ruN pjproject-2.10/pjsip/include/pjsip/sip_msg.h pjsip/pjsip/include/pjsip/sip_msg.h +--- pjproject-2.10/pjsip/include/pjsip/sip_msg.h 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/pjsip/include/pjsip/sip_msg.h 2021-02-06 19:48:20.570302003 +0100 +@@ -675,6 +675,23 @@ + }; + + /** ++ * General purpose function to print a SIP message body. ++ * Uses the appropriate internal functions to print the string representation ++ * of a SIP message body. It sets the output buffer to a statically allocated ++ * buffer, so the caller is responsible to copy it. ++ * ++ * @param msg_body The message body. ++ * @param buf Pointer to get the result buffer (statically allocated). ++ * @param size The size of the buffer. ++ * ++ * @return The length copied to the buffer, or -1. ++ */ ++PJ_DECL(int) pjsip_print_body( pjsip_msg_body *msg_body, ++ char **buf, int *len); ++ ++ ++ ++/** + * General purpose function to textual data in a SIP body. Attach this function + * in a SIP message body only if the data in pjsip_msg_body is a textual + * message ready to be embedded in a SIP message. If the data in the message +@@ -894,6 +911,20 @@ + PJ_DECL(void*) pjsip_msg_find_remove_hdr( pjsip_msg *msg, + pjsip_hdr_e hdr, void *start); + ++/** ++ * Find and remove a header in the message. ++ * ++ * @param msg The message. ++ * @param name The header name to find. ++ * @param start The first header field where the search should begin, ++ * or NULL to search from the first header in the message. ++ * ++ * @return The header field, or NULL if not found. ++ */ ++PJ_DECL(void*) pjsip_msg_find_remove_hdr_by_name( pjsip_msg *msg, ++ pj_str_t *name, ++ void *start); ++ + /** + * Add a header to the message, putting it last in the header list. + * +diff -ruN pjproject-2.10/pjsip/include/pjsip-simple/evsub.h pjsip/pjsip/include/pjsip-simple/evsub.h +--- pjproject-2.10/pjsip/include/pjsip-simple/evsub.h 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/pjsip/include/pjsip-simple/evsub.h 2021-02-06 19:49:09.127691820 +0100 +@@ -524,6 +524,18 @@ + pj_uint32_t seconds); + + ++/* Update evbsub internal refresh_time with the given interval */ ++PJ_DECL(void) pjsip_evsub_update_expires( pjsip_evsub *sub, ++ pj_uint32_t interval ); ++ ++ ++/* Set the specified timer (UAC or UAS) to the specified time */ ++PJ_DECL(void) pjsip_evsub_set_timer( pjsip_evsub *sub, ++ int timer_id, ++ pj_int32_t seconds ); ++ ++ ++ + PJ_END_DECL + + /** +diff -ruN pjproject-2.10/pjsip/src/pjsip/sip_msg.c pjsip/pjsip/src/pjsip/sip_msg.c +--- pjproject-2.10/pjsip/src/pjsip/sip_msg.c 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/pjsip/src/pjsip/sip_msg.c 2021-02-06 19:39:21.510782607 +0100 +@@ -394,6 +394,18 @@ + return hdr; + } + ++PJ_DEF(void*) pjsip_msg_find_remove_hdr_by_name( pjsip_msg *msg, ++ pj_str_t *name, ++ void *start) ++{ ++ pjsip_hdr *hdr = (pjsip_hdr*) pjsip_msg_find_hdr_by_name(msg, name, start); ++ if (hdr) { ++ pj_list_erase(hdr); ++ } ++ return hdr; ++} ++ ++ + PJ_DEF(pj_ssize_t) pjsip_msg_print( const pjsip_msg *msg, + char *buf, pj_size_t size) + { +@@ -2146,6 +2158,21 @@ + /* + * Message body manipulations. + */ ++ ++PJ_DEF(int) pjsip_print_body(pjsip_msg_body *msg_body, char **buf, int *len) ++{ ++ static char s_buf[PJSIP_MAX_PKT_LEN]; ++ int res; ++ ++ res = (*msg_body->print_body)(msg_body, s_buf, PJSIP_MAX_PKT_LEN); ++ if (res < 0) { ++ return -1; ++ } ++ *buf = s_buf; ++ *len = res; ++ return 0; ++} ++ + PJ_DEF(int) pjsip_print_text_body(pjsip_msg_body *msg_body, char *buf, pj_size_t size) + { + if (size < msg_body->len) +diff -ruN pjproject-2.10/pjsip/src/pjsip-simple/evsub.c pjsip/pjsip/src/pjsip-simple/evsub.c +--- pjproject-2.10/pjsip/src/pjsip-simple/evsub.c 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/pjsip/src/pjsip-simple/evsub.c 2021-02-06 19:40:40.321065814 +0100 +@@ -495,6 +495,12 @@ + } + + ++PJ_DEF(void) pjsip_evsub_update_expires( pjsip_evsub *sub, pj_uint32_t interval ) ++{ ++ update_expires(sub, interval); ++} ++ ++ + /* + * Schedule timer. + */ +@@ -538,6 +544,13 @@ + } + + ++PJ_DEF(void) pjsip_evsub_set_timer( pjsip_evsub *sub, int timer_id, ++ pj_int32_t seconds) ++{ ++ set_timer(sub, timer_id, seconds); ++} ++ ++ + /* + * Destructor. + */ +diff -ruN pjproject-2.10/pjsip/src/pjsip-simple/evsub_msg.c pjsip/pjsip/src/pjsip-simple/evsub_msg.c +--- pjproject-2.10/pjsip/src/pjsip-simple/evsub_msg.c 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/pjsip/src/pjsip-simple/evsub_msg.c 2021-02-06 19:41:44.190911650 +0100 +@@ -293,6 +293,45 @@ + } + + /* ++ * Parse Allow-Events header. ++ */ ++static pjsip_hdr* parse_hdr_allow_events(pjsip_parse_ctx *ctx) ++{ ++ pjsip_allow_events_hdr *allow_events = ++ pjsip_allow_events_hdr_create(ctx->pool); ++ const pjsip_parser_const_t *pc = pjsip_parser_const(); ++ pj_scanner *scanner = ctx->scanner; ++ ++ /* Some header fields allow empty elements in the value: ++ * Accept, Allow, Supported ++ */ ++ if (pj_scan_is_eof(scanner) || ++ *scanner->curptr == '\r' || *scanner->curptr == '\n') ++ { ++ goto end; ++ } ++ ++ pj_scan_get( scanner, &pc->pjsip_NOT_COMMA_OR_NEWLINE, ++ &allow_events->values[0]); ++ allow_events->count++; ++ ++ while (*scanner->curptr == ',') { ++ pj_scan_get_char(scanner); ++ pj_scan_get( scanner, &pc->pjsip_NOT_COMMA_OR_NEWLINE, ++ &allow_events->values[allow_events->count]); ++ allow_events->count++; ++ ++ if (allow_events->count >= PJSIP_MAX_ALLOW_EVENTS) ++ break; ++ } ++ ++end: ++ pjsip_parse_end_hdr_imp(scanner); ++ return (pjsip_hdr*)allow_events; ++} ++ ++ ++/* + * Register header parsers. + */ + PJ_DEF(void) pjsip_evsub_init_parser(void) +diff -ruN pjproject-2.10/pjsip/src/pjsip-ua/sip_inv.c pjsip/pjsip/src/pjsip-ua/sip_inv.c +--- pjproject-2.10/pjsip/src/pjsip-ua/sip_inv.c 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/pjsip/src/pjsip-ua/sip_inv.c 2021-02-06 19:46:19.602835344 +0100 +@@ -2006,6 +2006,20 @@ + return PJMEDIA_SDP_EINSDP; + } + ++ /* Only accept SDP in INVITE, UPDATE and ACK requests, 18x (reliable) and 183 provisional responses ++ * and 200 final response. ++ */ ++ if (!(msg->type == PJSIP_REQUEST_MSG && msg->line.req.method.id == PJSIP_INVITE_METHOD) && ++ !(msg->type == PJSIP_REQUEST_MSG && msg->line.req.method.id == PJSIP_ACK_METHOD) && ++ !(msg->type == PJSIP_REQUEST_MSG && pjsip_method_cmp(&msg->line.req.method, &pjsip_update_method)==0) && ++ !(msg->type == PJSIP_RESPONSE_MSG && msg->line.status.code/10==18 && pjsip_100rel_is_reliable(rdata)) && ++ !(msg->type == PJSIP_RESPONSE_MSG && msg->line.status.code == 183) && ++ !(msg->type == PJSIP_RESPONSE_MSG && msg->line.status.code == 200)) { ++ PJ_LOG(4,(inv->obj_name, "ignored SDP body")); ++ return PJ_SUCCESS; ++ } ++ ++ + /* Get/attach invite session's transaction data */ + tsx_inv_data = (struct tsx_inv_data*) tsx->mod_data[mod_inv.mod.id]; + if (tsx_inv_data == NULL) { +@@ -2233,6 +2247,11 @@ + { + status = pjmedia_sdp_neg_set_local_answer(inv->pool_prov, inv->neg, + local_sdp); ++ } else if (pjmedia_sdp_neg_get_state(inv->neg)== ++ PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER) ++ { ++ /* Go forward with our local offer */ ++ status = PJ_SUCCESS; + } else { + + /* Can not specify local SDP at this state. */ +@@ -3976,8 +3995,9 @@ + if (inv->state != PJSIP_INV_STATE_DISCONNECTED && + ((tsx->status_code == PJSIP_SC_CALL_TSX_DOES_NOT_EXIST && + tsx->method.id != PJSIP_CANCEL_METHOD) || +- tsx->status_code == PJSIP_SC_REQUEST_TIMEOUT || +- tsx->status_code == PJSIP_SC_TSX_TIMEOUT)) ++ (inv->state != PJSIP_INV_STATE_CONFIRMED && ++ (tsx->status_code == PJSIP_SC_TSX_TIMEOUT || ++ tsx->status_code == PJSIP_SC_TSX_TRANSPORT_ERROR)))) + { + pjsip_tx_data *bye; + pj_status_t status; +diff -ruN pjproject-2.10/third_party/build/os-auto.mak.in pjsip/third_party/build/os-auto.mak.in +--- pjproject-2.10/third_party/build/os-auto.mak.in 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/third_party/build/os-auto.mak.in 2021-02-06 16:09:49.029693848 +0100 +@@ -73,6 +73,8 @@ + endif + endif + ++DIRS += zsrtp ++ + ifneq (@ac_no_webrtc@,1) + ifeq (@ac_external_webrtc@,1) + # External webrtc +diff -ruN pjproject-2.10/third_party/build/os-darwinos.mak pjsip/third_party/build/os-darwinos.mak +--- pjproject-2.10/third_party/build/os-darwinos.mak 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/third_party/build/os-darwinos.mak 2021-02-06 16:00:34.131596843 +0100 +@@ -5,5 +5,6 @@ + DIRS += g7221 + DIRS += srtp + DIRS += resample ++DIRS += zsrtp + + +diff -ruN pjproject-2.10/third_party/build/os-linux.mak pjsip/third_party/build/os-linux.mak +--- pjproject-2.10/third_party/build/os-linux.mak 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/third_party/build/os-linux.mak 2021-02-06 15:59:58.326708529 +0100 +@@ -5,5 +5,6 @@ + DIRS += g7221 + DIRS += srtp + DIRS += resample ++DIRS += zsrtp + + +diff -ruN pjproject-2.10/third_party/build/os-win32.mak pjsip/third_party/build/os-win32.mak +--- pjproject-2.10/third_party/build/os-win32.mak 2020-02-14 10:48:27.000000000 +0100 ++++ pjsip/third_party/build/os-win32.mak 2021-02-06 16:00:20.023246385 +0100 +@@ -5,5 +5,6 @@ + DIRS += g7221 + DIRS += srtp + DIRS += resample ++DIRS += zsrtp + + diff --git a/deps/update_pjsip_210/patches/sipsimple_core.patch b/deps/update_pjsip_210/patches/sipsimple_core.patch new file mode 100644 index 00000000..e3e7bc0a --- /dev/null +++ b/deps/update_pjsip_210/patches/sipsimple_core.patch @@ -0,0 +1,155 @@ +diff -ruN sipsimple/core/_core.lib.pxi sipsimple/core/_core.lib.pxi +--- sipsimple/core/_core.lib.pxi 2021-02-18 22:20:23.927865045 +0100 ++++ sipsimple/core/_core.lib.pxi 2021-02-06 20:08:25.900660619 +0100 +@@ -269,7 +269,7 @@ + if status != 0: + raise PJSIPError("Could not initialize ffmpeg video codecs", status) + self._has_ffmpeg_video = 1 +- status = pjmedia_codec_vpx_init(NULL, &caching_pool._obj.factory) ++ status = pjmedia_codec_vpx_vid_init(NULL, &caching_pool._obj.factory) + if status != 0: + raise PJSIPError("Could not initialize vpx video codecs", status) + self._has_vpx = 1 +@@ -284,7 +284,7 @@ + if self._has_ffmpeg_video: + pjmedia_codec_ffmpeg_vid_deinit() + if self._has_vpx: +- pjmedia_codec_vpx_deinit() ++ pjmedia_codec_vpx_vid_deinit() + if pjmedia_vid_codec_mgr_instance() != NULL: + pjmedia_vid_codec_mgr_destroy(NULL) + if pjmedia_event_mgr_instance() != NULL: +@@ -298,7 +298,6 @@ + cdef unsigned int count = PJMEDIA_CODEC_MGR_MAX_CODECS + cdef pjmedia_codec_info info[PJMEDIA_CODEC_MGR_MAX_CODECS] + cdef unsigned int prio[PJMEDIA_CODEC_MGR_MAX_CODECS] +- cdef int i + cdef list retval + cdef int status + status = pjmedia_codec_mgr_enum_codecs(pjmedia_endpt_get_codec_mgr(self._obj), &count, info, prio) +@@ -372,7 +371,6 @@ + cdef unsigned int count = PJMEDIA_VID_CODEC_MGR_MAX_CODECS + cdef pjmedia_vid_codec_info info[PJMEDIA_VID_CODEC_MGR_MAX_CODECS] + cdef unsigned int prio[PJMEDIA_VID_CODEC_MGR_MAX_CODECS] +- cdef int i + cdef list retval + cdef int status + status = pjmedia_vid_codec_mgr_enum_codecs(NULL, &count, info, prio) +@@ -443,7 +441,6 @@ + cdef pjmedia_vid_codec_info info[PJMEDIA_VID_CODEC_MGR_MAX_CODECS] + cdef pjmedia_vid_codec_param vparam + cdef unsigned int prio[PJMEDIA_VID_CODEC_MGR_MAX_CODECS] +- cdef int i + cdef int status + cdef PJSTR h264_profile_level_id_value + cdef PJSTR h264_packetization_mode_value = PJSTR(b"1") # TODO; make it configurable? +@@ -482,7 +479,6 @@ + cdef pjmedia_vid_codec_info info[PJMEDIA_VID_CODEC_MGR_MAX_CODECS] + cdef pjmedia_vid_codec_param vparam + cdef unsigned int prio[PJMEDIA_VID_CODEC_MGR_MAX_CODECS] +- cdef int i + cdef int status + + max_width, max_height = max_resolution +diff -ruN sipsimple/core/_core.mediatransport.pxi sipsimple/core/_core.mediatransport.pxi +--- sipsimple/core/_core.mediatransport.pxi 2021-02-18 22:20:23.931865157 +0100 ++++ sipsimple/core/_core.mediatransport.pxi 2021-02-16 02:49:03.872618274 +0100 +@@ -397,7 +397,7 @@ + pj_remote_sdp = NULL + if sdp_index < 0: + raise ValueError("sdp_index argument cannot be negative") +- if sdp_index >= pj_local_sdp.media_count: ++ if sdp_index >= pj_local_sdp.media_count: + raise ValueError("sdp_index argument out of range") + with nogil: + status = pjmedia_transport_media_create(transport, pool, 0, pj_remote_sdp, sdp_index) +@@ -488,7 +488,6 @@ + def set_INIT(self): + global _ice_cb + cdef int af +- cdef int i + cdef int status + cdef int port + cdef pj_caching_pool *caching_pool +@@ -882,7 +881,7 @@ + pj_remote_sdp = NULL + if sdp_index < 0: + raise ValueError("sdp_index argument cannot be negative") +- if sdp_index >= pj_local_sdp.media_count: ++ if sdp_index >= pj_local_sdp.media_count: + raise ValueError("sdp_index argument out of range") + # Remove ICE and SRTP/ZRTP related attributes from SDP, they will be added by pjmedia_transport_encode_sdp + local_media = local_sdp.media[sdp_index] +diff -ruN sipsimple/core/_core.pxd sipsimple/core/_core.pxd +--- sipsimple/core/_core.pxd 2021-02-18 22:20:23.935865270 +0100 ++++ sipsimple/core/_core.pxd 2021-02-06 20:02:57.495316125 +0100 +@@ -906,8 +906,8 @@ + int pjmedia_codec_register_audio_codecs(pjmedia_endpt *endpt, const pjmedia_audio_codec_config *c) nogil + int pjmedia_codec_ffmpeg_vid_init(pjmedia_vid_codec_mgr *mgr, pj_pool_factory *pf) nogil + int pjmedia_codec_ffmpeg_vid_deinit() nogil +- int pjmedia_codec_vpx_init(pjmedia_vid_codec_mgr *mgr, pj_pool_factory *pf) nogil +- int pjmedia_codec_vpx_deinit() nogil ++ int pjmedia_codec_vpx_vid_init(pjmedia_vid_codec_mgr *mgr, pj_pool_factory *pf) nogil ++ int pjmedia_codec_vpx_vid_deinit() nogil + + cdef extern from "pjsip.h": + +diff -ruN sipsimple/core/_core.sdp.pxi sipsimple/core/_core.sdp.pxi +--- sipsimple/core/_core.sdp.pxi 2021-02-18 22:20:23.995866960 +0100 ++++ sipsimple/core/_core.sdp.pxi 2021-02-06 19:30:28.787506745 +0100 +@@ -966,7 +966,6 @@ + + cdef SDPSession SDPSession_create(pjmedia_sdp_session_ptr_const pj_session): + cdef SDPConnection connection = None +- cdef int i + if pj_session.conn != NULL: + connection = SDPConnection_create(pj_session.conn) + return SDPSession(_pj_str_to_bytes(pj_session.origin.addr), +@@ -985,7 +984,6 @@ + + cdef FrozenSDPSession FrozenSDPSession_create(pjmedia_sdp_session_ptr_const pj_session): + cdef FrozenSDPConnection connection = None +- cdef int i + if pj_session.conn != NULL: + connection = FrozenSDPConnection_create(pj_session.conn) + return FrozenSDPSession(_pj_str_to_bytes(pj_session.origin.addr), +@@ -1004,7 +1002,6 @@ + + cdef SDPMediaStream SDPMediaStream_create(pjmedia_sdp_media *pj_media): + cdef SDPConnection connection = None +- cdef int i + if pj_media.conn != NULL: + connection = SDPConnection_create(pj_media.conn) + return SDPMediaStream(_pj_str_to_bytes(pj_media.desc.media), +@@ -1018,7 +1015,6 @@ + + cdef FrozenSDPMediaStream FrozenSDPMediaStream_create(pjmedia_sdp_media *pj_media): + cdef FrozenSDPConnection connection = None +- cdef int i + if pj_media.conn != NULL: + connection = FrozenSDPConnection_create(pj_media.conn) + return FrozenSDPMediaStream(_pj_str_to_bytes(pj_media.desc.media), +diff -ruN sipsimple/core/_core.ua.pxi sipsimple/core/_core.ua.pxi +--- sipsimple/core/_core.ua.pxi 2021-02-18 22:20:24.003867185 +0100 ++++ sipsimple/core/_core.ua.pxi 2021-02-06 19:30:46.520008320 +0100 +@@ -1075,8 +1075,6 @@ + cdef pjmedia_sdp_session *sdp + cdef pjmedia_sdp_media *media + cdef pjmedia_sdp_attr *attr +- cdef int i +- cdef int j + cdef pj_str_t new_value + try: + ua = _get_ua() +diff -ruN sipsimple/core/_core.util.pxi sipsimple/core/_core.util.pxi +--- sipsimple/core/_core.util.pxi 2021-02-18 22:20:24.051868537 +0100 ++++ sipsimple/core/_core.util.pxi 2021-02-06 20:14:30.530825814 +0100 +@@ -219,7 +219,7 @@ + cdef pjsip_ctype_hdr *ctype_header + cdef pjsip_cseq_hdr *cseq_header + cdef char *buf +- cdef int buf_len, i, status ++ cdef int buf_len, status + headers = {} + header = ( &msg.hdr).next + while header != &msg.hdr: diff --git a/deps/update_pjsip_210/patches/zsrtp.patch b/deps/update_pjsip_210/patches/zsrtp.patch new file mode 100644 index 00000000..d6a84f34 --- /dev/null +++ b/deps/update_pjsip_210/patches/zsrtp.patch @@ -0,0 +1,302 @@ +--- pjsip_orig/third_party/zsrtp/zrtp/srtp/CryptoContext.h ++++ pjsip/third_party/zsrtp/zrtp/srtp/CryptoContext.h +@@ -419,7 +419,11 @@ + typedef union _hmacCtx { + SkeinCtx_t hmacSkeinCtx; + #ifdef ZRTP_OPENSSL ++ #if OPENSSL_VERSION_NUMBER < 0x10100000L + HMAC_CTX hmacSha1Ctx; ++ #else ++ HMAC_CTX * hmacSha1Ctx; ++ #endif + #else + hmacSha1Context hmacSha1Ctx; + #endif +--- pjsip_orig/third_party/zsrtp/zrtp/srtp/crypto/openssl/hmac.cpp ++++ pjsip/third_party/zsrtp/zrtp/srtp/crypto/openssl/hmac.cpp +@@ -36,21 +36,40 @@ + const std::vector& data, + const std::vector& dataLength, + uint8_t* mac, int32_t* macLength) { ++#if OPENSSL_VERSION_NUMBER < 0x10100000L + HMAC_CTX ctx = {}; + HMAC_CTX_init(&ctx); + HMAC_Init_ex(&ctx, key, static_cast(keyLength), EVP_sha1(), NULL); ++#else ++ HMAC_CTX* ctx; ++ ctx = HMAC_CTX_new(); ++ HMAC_Init_ex(ctx, key, static_cast(keyLength), EVP_sha1(), NULL); ++#endif + for (size_t i = 0, size = data.size(); i < size; i++) { ++#if OPENSSL_VERSION_NUMBER < 0x10100000L + HMAC_Update(&ctx, data[i], dataLength[i]); ++#else ++ HMAC_Update(ctx, data[i], dataLength[i]); ++#endif + } ++#if OPENSSL_VERSION_NUMBER < 0x10100000L + HMAC_Final(&ctx, mac, reinterpret_cast(macLength)); + HMAC_CTX_cleanup(&ctx); ++#else ++ HMAC_Final(ctx, mac, reinterpret_cast(macLength)); ++ HMAC_CTX_free( ctx ); ++#endif + } + + void* createSha1HmacContext(const uint8_t* key, uint64_t keyLength) + { ++#if OPENSSL_VERSION_NUMBER < 0x10100000L + auto* ctx = (HMAC_CTX*)malloc(sizeof(HMAC_CTX)); + + HMAC_CTX_init(ctx); ++#else ++ HMAC_CTX* ctx = HMAC_CTX_new(); ++#endif + HMAC_Init_ex(ctx, key, static_cast(keyLength), EVP_sha1(), nullptr); + return ctx; + } +@@ -59,7 +68,11 @@ + { + auto *pctx = (HMAC_CTX*)ctx; + ++#if OPENSSL_VERSION_NUMBER < 0x10100000L + HMAC_CTX_init(pctx); ++#else ++ HMAC_CTX_reset(pctx); ++#endif + HMAC_Init_ex(pctx, key, static_cast(keyLength), EVP_sha1(), nullptr); + return pctx; + } +@@ -69,9 +92,9 @@ + { + auto* pctx = (HMAC_CTX*)ctx; + +- HMAC_Init_ex(pctx, nullptr, 0, nullptr, nullptr); +- HMAC_Update(pctx, data, data_length ); +- HMAC_Final(pctx, mac, reinterpret_cast(mac_length) ); ++ HMAC_Init_ex( pctx, nullptr, 0, nullptr, nullptr ); ++ HMAC_Update( pctx, data, data_length ); ++ HMAC_Final( pctx, mac, reinterpret_cast(mac_length) ); + } + + void hmacSha1Ctx(void* ctx, +@@ -91,7 +114,11 @@ + void freeSha1HmacContext(void* ctx) + { + if (ctx) { ++#if OPENSSL_VERSION_NUMBER < 0x10100000L + HMAC_CTX_cleanup((HMAC_CTX*)ctx); + free(ctx); ++#else ++ HMAC_CTX_free((HMAC_CTX*)ctx); ++#endif + } + } +--- pjsip_orig/third_party/zsrtp/zrtp/zrtp/crypto/openssl/hmac256.cpp ++++ pjsip/third_party/zsrtp/zrtp/zrtp/crypto/openssl/hmac256.cpp +@@ -32,13 +32,31 @@ + uint8_t* mac, uint32_t* mac_length) + { + unsigned int tmp; ++#if OPENSSL_VERSION_NUMBER < 0x10100000L + HMAC_CTX ctx = {}; + HMAC_CTX_init(&ctx); + HMAC_Init_ex( &ctx, key, static_cast(key_length), EVP_sha256(), nullptr ); ++#else ++ HMAC_CTX * ctx; ++ ctx = HMAC_CTX_new(); ++ HMAC_Init_ex( ctx, key, static_cast(key_length), EVP_sha256(), nullptr ); ++#endif + for (size_t i = 0, size = data.size(); i < size; i++) { ++#if OPENSSL_VERSION_NUMBER < 0x10100000L + HMAC_Update(&ctx, data[i], dataLength[i]); ++#else ++ HMAC_Update( ctx, data[i], dataLength[i]); ++#endif + } ++#if OPENSSL_VERSION_NUMBER < 0x10100000L + HMAC_Final( &ctx, mac, &tmp); ++#else ++ HMAC_Final( ctx, mac, &tmp); ++#endif + *mac_length = tmp; ++#if OPENSSL_VERSION_NUMBER < 0x10100000L + HMAC_CTX_cleanup( &ctx ); ++#else ++ HMAC_CTX_free( ctx ); ++#endif + } +--- pjsip_orig/third_party/zsrtp/zrtp/zrtp/crypto/openssl/hmac384.cpp ++++ pjsip/third_party/zsrtp/zrtp/zrtp/crypto/openssl/hmac384.cpp +@@ -32,14 +32,32 @@ + uint8_t* mac, uint32_t* mac_length) + { + unsigned int tmp; ++#if OPENSSL_VERSION_NUMBER < 0x10100000L + HMAC_CTX ctx = {}; + HMAC_CTX_init( &ctx ); + HMAC_Init_ex( &ctx, key, static_cast(key_length), EVP_sha384(), nullptr ); + ++#else ++ HMAC_CTX * ctx; ++ ctx = HMAC_CTX_new(); ++ HMAC_Init_ex( ctx, key, static_cast(key_length), EVP_sha384(), nullptr ); ++#endif + for (size_t i = 0, size = data.size(); i < size; i++) { ++#if OPENSSL_VERSION_NUMBER < 0x10100000L + HMAC_Update(&ctx, data[i], dataLength[i]); ++#else ++ HMAC_Update( ctx, data[i], dataLength[i]); ++#endif + } ++#if OPENSSL_VERSION_NUMBER < 0x10100000L + HMAC_Final( &ctx, mac, &tmp); ++#else ++ HMAC_Final( ctx, mac, &tmp); ++#endif + *mac_length = tmp; ++#if OPENSSL_VERSION_NUMBER < 0x10100000L + HMAC_CTX_cleanup( &ctx ); ++#else ++ HMAC_CTX_free( ctx ); ++#endif + } +--- pjsip_orig/third_party/zsrtp/zrtp/zrtp/crypto/openssl/zrtpDH.cpp ++++ pjsip/third_party/zsrtp/zrtp/zrtp/crypto/openssl/zrtpDH.cpp +@@ -201,18 +201,41 @@ ZrtpDH::ZrtpDH(const char* type) { + case DH3K: + ctx = static_cast(DH_new()); + tmpCtx = static_cast(ctx); ++#if OPENSSL_VERSION_NUMBER < 0x10100000L + tmpCtx->g = BN_new(); + BN_set_word(tmpCtx->g, DH_GENERATOR_2); ++#else ++ { ++ BIGNUM* g = BN_new(); ++ BN_set_word(g, DH_GENERATOR_2); ++#endif + + if (pkType == DH2K) { ++#if OPENSSL_VERSION_NUMBER < 0x10100000L + tmpCtx->p = BN_dup(bnP2048); ++#else ++ DH_set0_pqg(tmpCtx, BN_dup(bnP2048), nullptr, g); ++#endif + RAND_bytes(random, 32); ++#if OPENSSL_VERSION_NUMBER < 0x10100000L + tmpCtx->priv_key = BN_bin2bn(random, 32, nullptr); ++#else ++ DH_set0_key(tmpCtx, NULL, BN_bin2bn(random, 32, nullptr)); ++#endif + } + else if (pkType == DH3K) { ++#if OPENSSL_VERSION_NUMBER < 0x10100000L + tmpCtx->p = BN_dup(bnP3072); ++#else ++ DH_set0_pqg(tmpCtx, BN_dup(bnP3072), nullptr, g); ++#endif + RAND_bytes(random, 64); ++#if OPENSSL_VERSION_NUMBER < 0x10100000L + tmpCtx->priv_key = BN_bin2bn(random, 32, nullptr); ++#else ++ DH_set0_key(tmpCtx, NULL, BN_bin2bn(random, 32, nullptr)); ++ } ++#endif + } + break; + +@@ -252,11 +275,18 @@ int32_t ZrtpDH::computeSecretKey(uint8_t *pubKeyBytes, uint8_t *secret) { + if (pkType == DH2K || pkType == DH3K) { + auto* tmpCtx = static_cast(ctx); + ++#if OPENSSL_VERSION_NUMBER < 0x10100000L + if (tmpCtx->pub_key != nullptr) { + BN_free(tmpCtx->pub_key); + } + tmpCtx->pub_key = BN_bin2bn(pubKeyBytes, getDhSize(), nullptr); + return DH_compute_key(secret, tmpCtx->pub_key, tmpCtx); ++#else ++ DH_set0_key(tmpCtx, BN_bin2bn(pubKeyBytes, getDhSize(), NULL), NULL); ++ BIGNUM* pub_key; ++ DH_get0_key(tmpCtx, const_cast(&pub_key), NULL); ++ return DH_compute_key(secret, pub_key, tmpCtx); ++#endif + } + if (pkType == EC25 || pkType == EC38) { + uint8_t buffer[200]; +@@ -304,8 +334,15 @@ int32_t ZrtpDH::getDhSize() const + + int32_t ZrtpDH::getPubKeySize() const + { +- if (pkType == DH2K || pkType == DH3K) +- return BN_num_bytes(static_cast(ctx)->pub_key); ++ if (pkType == DH2K || pkType == DH3K) { ++#if OPENSSL_VERSION_NUMBER < 0x10100000L ++ return BN_num_bytes(static_cast(ctx)->pub_key); ++#else ++ BIGNUM* pub_key; ++ DH_get0_key(static_cast(ctx), const_cast(&pub_key), NULL); ++ return BN_num_bytes(pub_key); ++#endif ++ } + + if (pkType == EC25 || pkType == EC38) + return EC_POINT_point2oct(EC_KEY_get0_group(static_cast(ctx)), +@@ -324,7 +361,13 @@ int32_t ZrtpDH::getPubKeyBytes(uint8_t *buf) const + if (prepend > 0) { + memset(buf, 0, prepend); + } ++#if OPENSSL_VERSION_NUMBER < 0x10100000L + return BN_bn2bin(static_cast(ctx)->pub_key, buf + prepend); ++#else ++ BIGNUM* pub_key; ++ DH_get0_key(static_cast(ctx), const_cast(&pub_key), NULL); ++ return BN_bn2bin(pub_key, buf + prepend); ++#endif + } + if (pkType == EC25 || pkType == EC38) { + uint8_t buffer[200]; +--- pjsip/third_party/build/zsrtp/Makefile 2021-02-18 22:53:28.796756816 +0100 ++++ pjsip/third_party/build/zsrtp/Makefile 2021-02-06 16:30:32.687434185 +0100 +@@ -1,5 +1,6 @@ + include ../../../build.mak + include ../../../build/common.mak ++include ../os-$(OS_NAME).mak + + export LIBDIR := ../../lib + +@@ -5,8 +6,14 @@ + + RULES_MAK := $(PJDIR)/build/rules.mak + +-export ZSRTP_LIB := ../../lib/libzsrtp-$(TARGET_NAME)$(LIBEXT) ++export ZSRTP_LIB := libzsrtp-$(TARGET_NAME)$(LIBEXT) + ++ifeq ($(PJ_SHARED_LIBRARIES),) ++else ++export ZSRTP_SONAME := libzsrtp.$(SHLIB_SUFFIX) ++export ZSRTP_SHLIB := $(ZSRTP_SONAME).$(PJ_VERSION_MAJOR) ++endif ++ + ############################################################################### + # Gather all flags. + # +@@ -112,8 +119,16 @@ + + .PHONY: dep depend libzsrtp clean realclean distclean + +-libzsrtp: +- $(MAKE) -f $(RULES_MAK) APP=ZSRTP app=libzsrtp $(ZSRTP_LIB) ++.PHONY: $(TARGETS) ++.PHONY: $(ZSRTP_LIB) $(ZSRTP_SONAME) ++ ++libzsrtp: $(ZSRTP_LIB) ++$(ZSRTP_SONAME): $(ZSRTP_LIB) ++$(ZSRTP_LIB) $(ZSRTP_SONAME): ++ $(MAKE) -f $(RULES_MAK) APP=ZSRTP app=libzsrtp $(subst /,$(HOST_PSEP),$(LIBDIR)/$@) ++ ++#$(MAKE) -f $(RULES_MAK) APP=ZSRTP app=libzsrtp $(ZSRTP_LIB) ++ + + clean print_lib: + $(MAKE) -f $(RULES_MAK) APP=ZSRTP app=libzsrtp $@ diff --git a/deps/update_pjsip_210/update_pjsip.sh b/deps/update_pjsip_210/update_pjsip.sh new file mode 100755 index 00000000..893f4b43 --- /dev/null +++ b/deps/update_pjsip_210/update_pjsip.sh @@ -0,0 +1,71 @@ +#!/bin/bash + +# +# Update PJSIP +# + +# Get latest stable release from github +wget --no-check-certificate https://github.com/pjsip/pjproject/archive/2.10.tar.gz +tar xzf 2.10.tar.gz +rm 2.10.tar.gz + + +echo Copying old files +mkdir old +mv ../pjsip old/ +echo Updating pjsip +mv pjproject* ../pjsip + + + +# +# Update ZSRTP +# + +# Clone latest version from github +git clone https://github.com/wernerd/ZRTPCPP.git + +# Copy wrapper from old version to third_party/zsrtp/ +echo Copyng zsrtp wrapper from old version to third_party/zsrtp/ +mkdir ../pjsip/third_party/zsrtp +cp -r old/pjsip/third_party/zsrtp/include ../pjsip/third_party/zsrtp/ +cp -r old/pjsip/third_party/zsrtp/srtp ../pjsip/third_party/zsrtp/ +cp -r old/pjsip/third_party/build/zsrtp ../pjsip/third_party/build/ + +# Copy new version to third_party/zsrtp/ +echo Copying new version of zrtp to third_party/zsrtp/ +mkdir ../pjsip/third_party/zsrtp/zrtp +cp -r ZRTPCPP/bnlib ../pjsip/third_party/zsrtp/zrtp/ +cp -r ZRTPCPP/common ../pjsip/third_party/zsrtp/zrtp/ +cp -r ZRTPCPP/cryptcommon ../pjsip/third_party/zsrtp/zrtp/ +cp -r ZRTPCPP/srtp ../pjsip/third_party/zsrtp/zrtp/ +cp -r ZRTPCPP/zrtp ../pjsip/third_party/zsrtp/zrtp/ +cp ZRTPCPP/COPYING ../pjsip/third_party/zsrtp/zrtp/ +cp ZRTPCPP/README.md ../pjsip/third_party/zsrtp/zrtp/ + +# Clean update directory +echo Cleaning update directory +rm -rf ZRTPCPP + +# Clean old directory +echo Cleaning old directory +rm -rf old + +cd .. + +echo Applying pjsip patches +cat update_pjsip_210/patches/pjsip_210.patch | patch -p0 + + +echo Applying zsrtp patches +cat update_pjsip_210/patches/zsrtp.patch | patch -p0 + +cd .. + +echo Applying sipsimple/core patches +cat deps/update_pjsip_210/patches/sipsimple_core.patch | patch -p0 + +cd - + +echo Done +