From 2068f13bc42cf3a47374aa2765f82724a5782028 Mon Sep 17 00:00:00 2001 From: Liong Sauw Ming Date: Mon, 24 Oct 2011 09:28:13 +0000 Subject: Re #1395: Backport of PJSIP 1.x branch into PJSIP 2.0 trunk * Backport of r3557:r3832 TODO: ticket #1268 (Option for automatic/manual sending of RTCP SDES/BYE for the stream) for video stream. git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@3841 74dad513-b988-da41-8d7b-12977e46ad98 --- pjmedia/build/Jbtest.dat | 39 +- pjmedia/build/os-auto.mak.in | 7 + pjmedia/build/pjmedia_codec.vcproj | 8 + pjmedia/include/pjmedia-audiodev/config.h | 15 + pjmedia/include/pjmedia-codec.h | 1 + pjmedia/include/pjmedia-codec/amr_helper.h | 17 +- pjmedia/include/pjmedia-codec/config.h | 33 + pjmedia/include/pjmedia-codec/config_auto.h.in | 5 + pjmedia/include/pjmedia-codec/opencore_amrnb.h | 89 +++ pjmedia/include/pjmedia/config.h | 71 +- pjmedia/include/pjmedia/delaybuf.h | 20 +- pjmedia/include/pjmedia/echo.h | 10 +- pjmedia/include/pjmedia/jbuf.h | 52 +- pjmedia/include/pjmedia/session.h | 20 + pjmedia/include/pjmedia/stream.h | 23 + pjmedia/include/pjmedia/transport_adapter_sample.h | 9 +- pjmedia/src/pjmedia-audiodev/audiodev.c | 10 +- pjmedia/src/pjmedia-audiodev/coreaudio_dev.c | 12 +- pjmedia/src/pjmedia-audiodev/symb_aps_dev.cpp | 4 +- pjmedia/src/pjmedia-audiodev/symb_mda_dev.cpp | 84 ++- pjmedia/src/pjmedia-audiodev/symb_vas_dev.cpp | 25 +- pjmedia/src/pjmedia-codec/audio_codecs.c | 7 + pjmedia/src/pjmedia-codec/ipp_codecs.c | 13 +- pjmedia/src/pjmedia-codec/opencore_amrnb.c | 820 +++++++++++++++++++++ pjmedia/src/pjmedia-codec/passthrough.c | 2 + pjmedia/src/pjmedia/alaw_ulaw.c | 6 + pjmedia/src/pjmedia/delaybuf.c | 97 +-- pjmedia/src/pjmedia/echo_common.c | 5 +- pjmedia/src/pjmedia/jbuf.c | 369 ++++++---- pjmedia/src/pjmedia/sdp_neg.c | 5 +- pjmedia/src/pjmedia/session.c | 34 + pjmedia/src/pjmedia/stream.c | 96 ++- pjmedia/src/pjmedia/transport_adapter_sample.c | 7 +- pjmedia/src/pjmedia/transport_ice.c | 66 +- pjmedia/src/pjmedia/transport_udp.c | 53 +- pjmedia/src/test/jbuf_test.c | 11 +- pjmedia/src/test/mips_test.c | 40 + pjmedia/src/test/test.c | 11 + 38 files changed, 1869 insertions(+), 327 deletions(-) create mode 100644 pjmedia/include/pjmedia-codec/opencore_amrnb.h create mode 100644 pjmedia/src/pjmedia-codec/opencore_amrnb.c (limited to 'pjmedia') diff --git a/pjmedia/build/Jbtest.dat b/pjmedia/build/Jbtest.dat index b2f4999d..4f5a67fd 100644 --- a/pjmedia/build/Jbtest.dat +++ b/pjmedia/build/Jbtest.dat @@ -19,11 +19,11 @@ # # 3. Success conditions, started with '!', followed by condition name # and its maximum tolerable value, in frames unit. Recognized condition -# names are: burst, discard, lost, empty, delay. These conditions will -# be verified with jitter buffer statistics after all session test data -# are executed. +# names are: burst, discard, lost, empty, delay, delay_min. These +# conditions will be verified with jitter buffer statistics after all +# session test data are executed. # Example: -# !delay 10 <- maximum average delay of jbuf is 10 frames +# !delay 10 <- average delay of jbuf is 10 frames # # 4. Session test data, containing sequence of jitter buffer events, # an event is represented by a character as follow: @@ -295,7 +295,7 @@ PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG !discard 50 <- frames discarded for delay adaptation !lost 50 <- ticket #1188, normal frame after discarded frame is flagged 'lost' to align signal !empty 0 -!delay 25 <- average delay, JB is able to adapt the delay +!delay_min 2 <- minimum delay, JB is able to adapt the delay PPPPPPPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPPPP PPPPPPPPPP PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG @@ -311,13 +311,30 @@ PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG +PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG +PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG +PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG +PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG +PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG +PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG +PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG +PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG +PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG +PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG +PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG +PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG +PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG +PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG +PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG +PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG +PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG . = Fixed mode prefetch 5, with two empty events %fixed 5 !burst 1 -!discard 0 -!lost 0 +!discard 4 <- the burst level is about 1, but prefetching will cause delay by 5 frames prefetching, delay adjustment may take place later on +!lost 4 <- progressive discard drops frames as if they were lost !empty 10 !delay 5 G @@ -329,8 +346,8 @@ PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG = Fixed mode prefetch 5, with random burst %fixed 5 !burst 3 -!discard 0 -!lost 0 +!discard 4 <- (see above test scenario) +!lost 4 <- (see above test scenario) !empty 5 !delay 5 PGPGPPGGPPPPGGPGGGPG PGGGGPPPGPPGPPPGGPGG PGPGPPGGPPPPGGPGGGPG @@ -349,7 +366,7 @@ PGGGGPPPGPPGPPPGGPGG PGPGPPGGPPGGPPPGGGPG PGGGGPPPGPPGPPPGGPGG !discard 50 <- frames discarded for delay adaptation !lost 50 <- ticket #1188, normal frame after discarded frame is flagged 'lost' to align signal !empty 0 -!delay 20 <- average delay, twice of minimal prefetch +!delay_min 20 <- minimum delay, twice of minimal prefetch PPPPPPPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPPPP PPPPPPPPPP PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG @@ -369,7 +386,7 @@ PGPGPGPGPGPGPGPGPGPG PGPGPGPGPGPGPGPGPGPG PGPGPGPGPG = Large PUT burst at beginning, then normal with burst level 10 and periodic burst spikes %adaptive 0 0 40 -!burst 10 +!burst 12 !discard 300 <- not so relevant for long period session with many delay adjustments needed (i.e: for first burst and periodic spikes) !lost 300 <- ticket #1188, normal frame after discarded frame is flagged 'lost' to align signal !empty 60 <- delay adjustment effect, as there is actually no drift diff --git a/pjmedia/build/os-auto.mak.in b/pjmedia/build/os-auto.mak.in index 600b0345..98ecf288 100644 --- a/pjmedia/build/os-auto.mak.in +++ b/pjmedia/build/os-auto.mak.in @@ -57,6 +57,7 @@ AC_NO_SPEEX_CODEC=@ac_no_speex_codec@ AC_NO_ILBC_CODEC=@ac_no_ilbc_codec@ AC_NO_G722_CODEC=@ac_no_g722_codec@ AC_NO_G7221_CODEC=@ac_no_g7221_codec@ +AC_NO_OPENCORE_AMRNB=@ac_no_opencore_amrnb@ export CODEC_OBJS= @@ -109,6 +110,12 @@ export CODEC_OBJS += g7221.o export G7221_CFLAGS += -I$(THIRD_PARTY) endif +ifeq ($(AC_NO_OPENCORE_AMRNB),1) +export CFLAGS += -DPJMEDIA_HAS_OPENCORE_AMRNB_CODEC=0 +else +export CODEC_OBJS += opencore_amrnb.o +endif + # # PortAudio diff --git a/pjmedia/build/pjmedia_codec.vcproj b/pjmedia/build/pjmedia_codec.vcproj index 6f09b2ae..516c011e 100644 --- a/pjmedia/build/pjmedia_codec.vcproj +++ b/pjmedia/build/pjmedia_codec.vcproj @@ -2966,6 +2966,10 @@ /> + + @@ -3101,6 +3105,10 @@ RelativePath="..\include\pjmedia-codec\l16.h" > + + diff --git a/pjmedia/include/pjmedia-audiodev/config.h b/pjmedia/include/pjmedia-audiodev/config.h index 2ef9e9f6..d0df6756 100644 --- a/pjmedia/include/pjmedia-audiodev/config.h +++ b/pjmedia/include/pjmedia-audiodev/config.h @@ -144,6 +144,21 @@ PJ_BEGIN_DECL #endif +/** + * This setting controls whether the Symbian audio with built-in multimedia + * framework backend should be started synchronously. Note that synchronous + * start will block the application/UI, e.g: about 40ms for each direction + * on N95. While asynchronous start may cause invalid value (always zero) + * returned in input/output volume query, if the query is performed when + * the internal start procedure is not completely finished. + * + * Default: 1 (yes) + */ +#ifndef PJMEDIA_AUDIO_DEV_MDA_USE_SYNC_START +# define PJMEDIA_AUDIO_DEV_MDA_USE_SYNC_START 1 +#endif + + /** * This setting controls whether the Audio Device API should support * device implementation that is based on the old sound device API diff --git a/pjmedia/include/pjmedia-codec.h b/pjmedia/include/pjmedia-codec.h index 7e77b1d7..9eba4c11 100644 --- a/pjmedia/include/pjmedia-codec.h +++ b/pjmedia/include/pjmedia-codec.h @@ -35,6 +35,7 @@ #include #include #include +#include #endif /* __PJMEDIA_CODEC_PJMEDIA_CODEC_H__ */ diff --git a/pjmedia/include/pjmedia-codec/amr_helper.h b/pjmedia/include/pjmedia-codec/amr_helper.h index 6bca5ac1..fc63eb47 100644 --- a/pjmedia/include/pjmedia-codec/amr_helper.h +++ b/pjmedia/include/pjmedia-codec/amr_helper.h @@ -567,7 +567,7 @@ const pj_int16_t* const pjmedia_codec_amrwb_ordermaps[9] = * Constant of AMR-NB frame lengths in bytes. */ const pj_uint8_t pjmedia_codec_amrnb_framelen[16] = - {12, 13, 15, 17, 19, 20, 26, 31, 5, 0, 0, 0, 0, 0, 0, 5}; + {12, 13, 15, 17, 19, 20, 26, 31, 5, 0, 0, 0, 0, 0, 0, 0}; /** * Constant of AMR-NB frame lengths in bits. */ @@ -583,7 +583,7 @@ const pj_uint16_t pjmedia_codec_amrnb_bitrates[8] = * Constant of AMR-WB frame lengths in bytes. */ const pj_uint8_t pjmedia_codec_amrwb_framelen[16] = - {17, 23, 32, 37, 40, 46, 50, 58, 60, 5, 0, 0, 0, 0, 0, 5}; + {17, 23, 32, 37, 40, 46, 50, 58, 60, 5, 0, 0, 0, 0, 0, 0}; /** * Constant of AMR-WB frame lengths in bits. */ @@ -606,6 +606,7 @@ typedef struct pjmedia_codec_amr_bit_info { pj_int8_t mode; /**< AMR mode. */ pj_uint8_t start_bit; /**< Frame start bit. */ pj_uint8_t good_quality:1; /**< Flag if frame is good/degraded. */ + pj_uint8_t STI:1; /**< STI mode (first/update). */ } pjmedia_codec_amr_bit_info; #pragma pack() @@ -1020,9 +1021,7 @@ PJ_INLINE (pj_status_t) pjmedia_codec_amr_pack( } else if (info->frame_type == SID_FT) { /* SID */ - pj_uint8_t STI = 0; - - amr_bits[35] = (pj_uint8_t)(STI & 1); + amr_bits[35] |= info->STI; if (setting->amr_nb) { amr_bits[36] = (pj_uint8_t)((info->mode >> 2) & 1); @@ -1163,6 +1162,7 @@ PJ_INLINE(pj_status_t) pjmedia_codec_amr_parse( info->mode = (pj_int8_t)((FT < SID_FT)? FT : -1); info->good_quality = (pj_uint8_t)(Q == 1); info->start_bit = 0; + info->STI = 0; frames[cnt].timestamp = ts_; frames[cnt].type = PJMEDIA_FRAME_TYPE_AUDIO; @@ -1186,6 +1186,13 @@ PJ_INLINE(pj_status_t) pjmedia_codec_amr_parse( frames[cnt].buf = r; info->start_bit = r_bitptr; + if (FT == SID_FT) { + unsigned sti_bitptr; + sti_bitptr = r_bitptr + 35; + info->STI = (pj_uint8_t) + (r[sti_bitptr >> 3] >> (7 - (sti_bitptr % 8))) & 1; + } + if (setting->octet_aligned) { r += framelen_tbl[FT]; frames[cnt].size = framelen_tbl[FT]; diff --git a/pjmedia/include/pjmedia-codec/config.h b/pjmedia/include/pjmedia-codec/config.h index 6b55baa2..0f94a98c 100644 --- a/pjmedia/include/pjmedia-codec/config.h +++ b/pjmedia/include/pjmedia-codec/config.h @@ -312,6 +312,39 @@ # define PJMEDIA_HAS_G7221_CODEC 0 #endif +/** + * Enable OpenCORE AMR-NB codec. + * See https://trac.pjsip.org/repos/ticket/1388 for some info. + * + * Default: 0 + */ +#ifndef PJMEDIA_HAS_OPENCORE_AMRNB_CODEC +# define PJMEDIA_HAS_OPENCORE_AMRNB_CODEC 0 +#endif + +/** + * Link with libopencore-amrXX via pragma comment on Visual Studio. + * This option only makes sense if PJMEDIA_HAS_OPENCORE_AMRNB_CODEC + * is enabled. + * + * Default: 1 + */ +#ifndef PJMEDIA_AUTO_LINK_OPENCORE_AMR_LIBS +# define PJMEDIA_AUTO_LINK_OPENCORE_AMR_LIBS 1 +#endif + +/** + * Link with libopencore-amrXX.a that has been produced with gcc. + * This option only makes sense if PJMEDIA_HAS_OPENCORE_AMRNB_CODEC + * and PJMEDIA_AUTO_LINK_OPENCORE_AMR_LIBS are enabled. + * + * Default: 1 + */ +#ifndef PJMEDIA_OPENCORE_AMR_BUILT_WITH_GCC +# define PJMEDIA_OPENCORE_AMR_BUILT_WITH_GCC 1 +#endif + + /** * Default G.722.1 codec encoder and decoder level adjustment. * If the value is non-zero, then PCM input samples to the encoder will diff --git a/pjmedia/include/pjmedia-codec/config_auto.h.in b/pjmedia/include/pjmedia-codec/config_auto.h.in index 9c2a9af2..c469d795 100644 --- a/pjmedia/include/pjmedia-codec/config_auto.h.in +++ b/pjmedia/include/pjmedia-codec/config_auto.h.in @@ -69,6 +69,11 @@ #undef PJMEDIA_HAS_G7221_CODEC #endif +/* OpenCORE AMR-NB codec */ +#ifndef PJMEDIA_HAS_OPENCORE_AMRNB_CODEC +#undef PJMEDIA_HAS_OPENCORE_AMRNB_CODEC +#endif + #endif /* __PJMEDIA_CODEC_CONFIG_AUTO_H_ */ diff --git a/pjmedia/include/pjmedia-codec/opencore_amrnb.h b/pjmedia/include/pjmedia-codec/opencore_amrnb.h new file mode 100644 index 00000000..3e618e46 --- /dev/null +++ b/pjmedia/include/pjmedia-codec/opencore_amrnb.h @@ -0,0 +1,89 @@ +/* $Id$ */ +/* + * Copyright (C) 2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2011 Dan Arrhenius + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef __PJMEDIA_CODEC_OPENCORE_AMRNB_H__ +#define __PJMEDIA_CODEC_OPENCORE_AMRNB_H__ + +#include + +/** + * @defgroup PJMED_OC_AMRNB OpenCORE AMR-NB Codec + * @ingroup PJMEDIA_CODEC_CODECS + * @brief AMRCodec wrapper for OpenCORE AMR-NB codec + * @{ + */ + +PJ_BEGIN_DECL + +/** + * Settings. Use #pjmedia_codec_opencore_amrnb_set_config() to + * activate. + */ +typedef struct pjmedia_codec_amrnb_config +{ + /** + * Control whether to use octent align. + */ + pj_bool_t octet_align; + + /** + * Set the bitrate. + */ + unsigned bitrate; + +} pjmedia_codec_amrnb_config; + + +/** + * Initialize and register AMR-NB codec factory to pjmedia endpoint. + * + * @param endpt The pjmedia endpoint. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_codec_opencore_amrnb_init(pjmedia_endpt* endpt); + +/** + * Unregister AMR-NB codec factory from pjmedia endpoint and deinitialize + * the OpenCORE codec library. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_codec_opencore_amrnb_deinit(void); + + +/** + * Set AMR-NB parameters. + * + * @param cfg The settings; + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) pjmedia_codec_opencore_amrnb_set_config( + const pjmedia_codec_amrnb_config* cfg); + +PJ_END_DECL + + +/** + * @} + */ + +#endif /* __PJMEDIA_CODEC_OPENCORE_AMRNB_H__ */ + diff --git a/pjmedia/include/pjmedia/config.h b/pjmedia/include/pjmedia/config.h index e7d2e149..60930818 100644 --- a/pjmedia/include/pjmedia/config.h +++ b/pjmedia/include/pjmedia/config.h @@ -387,7 +387,7 @@ /** - * Number of packets received from different source IP address from the + * Number of RTP packets received from different source IP address from the * remote address required to make the stream switch transmission * to the source address. */ @@ -396,6 +396,16 @@ #endif +/** + * Number of RTCP packets received from different source IP address from the + * remote address required to make the stream switch RTCP transmission + * to the source address. + */ +#ifndef PJMEDIA_RTCP_NAT_PROBATION_CNT +# define PJMEDIA_RTCP_NAT_PROBATION_CNT 3 +#endif + + /** * Specify whether RTCP should be advertised in SDP. This setting would * affect whether RTCP candidate will be added in SDP when ICE is used. @@ -916,7 +926,7 @@ * Default: 5 seconds */ #ifndef PJMEDIA_STREAM_KA_INTERVAL -# define PJMEDIA_STREAM_KA_INTERVAL 5 +# define PJMEDIA_STREAM_KA_INTERVAL 5 #endif @@ -1056,6 +1066,63 @@ #endif +/** + * Minimum gap between two consecutive discards in jitter buffer, + * in milliseconds. + * + * Default: 200 ms + */ +#ifndef PJMEDIA_JBUF_DISC_MIN_GAP +# define PJMEDIA_JBUF_DISC_MIN_GAP 200 +#endif + + +/** + * Minimum burst level reference used for calculating discard duration + * in jitter buffer progressive discard algorithm, in frames. + * + * Default: 1 frame + */ +#ifndef PJMEDIA_JBUF_PRO_DISC_MIN_BURST +# define PJMEDIA_JBUF_PRO_DISC_MIN_BURST 1 +#endif + + +/** + * Maximum burst level reference used for calculating discard duration + * in jitter buffer progressive discard algorithm, in frames. + * + * Default: 200 frames + */ +#ifndef PJMEDIA_JBUF_PRO_DISC_MAX_BURST +# define PJMEDIA_JBUF_PRO_DISC_MAX_BURST 100 +#endif + + +/** + * Duration for progressive discard algotithm in jitter buffer to discard + * an excessive frame when burst is equal to or lower than + * PJMEDIA_JBUF_PRO_DISC_MIN_BURST, in milliseconds. + * + * Default: 2000 ms + */ +#ifndef PJMEDIA_JBUF_PRO_DISC_T1 +# define PJMEDIA_JBUF_PRO_DISC_T1 2000 +#endif + + +/** + * Duration for progressive discard algotithm in jitter buffer to discard + * an excessive frame when burst is equal to or lower than + * PJMEDIA_JBUF_PRO_DISC_MAX_BURST, in milliseconds. + * + * Default: 10000 ms + */ +#ifndef PJMEDIA_JBUF_PRO_DISC_T2 +# define PJMEDIA_JBUF_PRO_DISC_T2 10000 +#endif + + /** * Video stream will discard old picture from the jitter buffer as soon as * new picture is received, to reduce latency. diff --git a/pjmedia/include/pjmedia/delaybuf.h b/pjmedia/include/pjmedia/delaybuf.h index 9dcad5ee..09b01bbb 100644 --- a/pjmedia/include/pjmedia/delaybuf.h +++ b/pjmedia/include/pjmedia/delaybuf.h @@ -63,6 +63,19 @@ PJ_BEGIN_DECL /** Opaque declaration for delay buffer. */ typedef struct pjmedia_delay_buf pjmedia_delay_buf; +/** + * Delay buffer options. + */ +typedef enum pjmedia_delay_buf_flag +{ + /** + * Use simple FIFO mechanism for the delay buffer, i.e. + * without WSOLA for expanding and shrinking audio samples. + */ + PJMEDIA_DELAY_BUF_SIMPLE_FIFO = 1 + +} pjmedia_delay_buf_flag; + /** * Create the delay buffer. Once the delay buffer is created, it will * enter learning state unless the delay argument is specified, which @@ -79,7 +92,12 @@ typedef struct pjmedia_delay_buf pjmedia_delay_buf; * in ms, if this value is negative or less than * one frame time, default maximum delay used is * 400 ms. - * @param options Option flags, must be zero for now. + * @param options Options. If PJMEDIA_DELAY_BUF_SIMPLE_FIFO is + * specified, then a simple FIFO mechanism + * will be used instead of the adaptive + * implementation (which uses WSOLA to expand + * or shrink audio samples). + * See #pjmedia_delay_buf_flag for other options. * @param p_b Pointer to receive the delay buffer instance. * * @return PJ_SUCCESS if the delay buffer has been diff --git a/pjmedia/include/pjmedia/echo.h b/pjmedia/include/pjmedia/echo.h index f2c2210c..ba7f8897 100644 --- a/pjmedia/include/pjmedia/echo.h +++ b/pjmedia/include/pjmedia/echo.h @@ -88,7 +88,15 @@ typedef enum pjmedia_echo_flag * for the echo canceller, but application will guarantee that echo * canceller will not be called by different threads at the same time. */ - PJMEDIA_ECHO_NO_LOCK = 16 + PJMEDIA_ECHO_NO_LOCK = 16, + + /** + * If PJMEDIA_ECHO_USE_SIMPLE_FIFO flag is specified, the delay buffer + * created for the echo canceller will use simple FIFO mechanism, i.e. + * without using WSOLA to expand and shrink audio samples. + */ + PJMEDIA_ECHO_USE_SIMPLE_FIFO = 32 + } pjmedia_echo_flag; diff --git a/pjmedia/include/pjmedia/jbuf.h b/pjmedia/include/pjmedia/jbuf.h index 2ae4ed6c..2cda3dd9 100644 --- a/pjmedia/include/pjmedia/jbuf.h +++ b/pjmedia/include/pjmedia/jbuf.h @@ -48,7 +48,7 @@ PJ_BEGIN_DECL /** * Types of frame returned by the jitter buffer. */ -enum pjmedia_jb_frame_type +typedef enum pjmedia_jb_frame_type { PJMEDIA_JB_MISSING_FRAME = 0, /**< No frame because it's missing */ PJMEDIA_JB_NORMAL_FRAME = 1, /**< Normal frame is being returned */ @@ -56,13 +56,41 @@ enum pjmedia_jb_frame_type because JB is bufferring. */ PJMEDIA_JB_ZERO_EMPTY_FRAME = 3 /**< Zero frame is being returned because JB is empty. */ -}; +} pjmedia_jb_frame_type; /** - * @see pjmedia_jb_frame_type. + * Enumeration of jitter buffer discard algorithm. The jitter buffer + * continuously calculates the jitter level to get the optimum latency at + * any time and in order to adjust the latency, the jitter buffer may need + * to discard some frames. */ -typedef enum pjmedia_jb_frame_type pjmedia_jb_frame_type; +typedef enum pjmedia_jb_discard_algo +{ + /** + * Jitter buffer should not discard any frame, except when the jitter + * buffer is full and a new frame arrives, one frame will be discarded + * to make space for the new frame. + */ + PJMEDIA_JB_DISCARD_NONE = 0, + + /** + * Only discard one frame in at least 200ms when the latency is considered + * much higher than it should be. When the jitter buffer is full and a new + * frame arrives, one frame will be discarded to make space for the new + * frame. + */ + PJMEDIA_JB_DISCARD_STATIC, + + /** + * The discard rate is dynamically calculated based on actual parameters + * such as jitter level and latency. When the jitter buffer is full and + * a new frame arrives, one frame will be discarded to make space for the + * new frame. + */ + PJMEDIA_JB_DISCARD_PROGRESSIVE + +} pjmedia_jb_discard_algo; /** @@ -107,7 +135,9 @@ typedef struct pjmedia_jbuf pjmedia_jbuf; /** * Create an adaptive jitter buffer according to the specification. If * application wants to have a fixed jitter buffer, it may call - * #pjmedia_jbuf_set_fixed() after the jitter buffer is created. + * #pjmedia_jbuf_set_fixed() after the jitter buffer is created. Also + * if application wants to alter the discard algorithm, which the default + * PJMEDIA_JB_DISCARD_PROGRESSIVE, it may call #pjmedia_jbuf_set_discard(). * * This function may allocate large chunk of memory to keep the frames in * the buffer. @@ -174,16 +204,16 @@ PJ_DECL(pj_status_t) pjmedia_jbuf_set_adaptive( pjmedia_jbuf *jb, /** - * Enable/disable the jitter buffer drift detection and handling mechanism. - * The default behavior is enabled. + * Set the jitter buffer discard algorithm. The default discard algorithm, + * set in jitter buffer creation, is PJMEDIA_JB_DISCARD_PROGRESSIVE. * - * @param jb The jitter buffer - * @param enable Set to PJ_TRUE to enable or PJ_FALSE to disable. + * @param jb The jitter buffer. + * @param algo The discard algorithm to be used. * * @return PJ_SUCCESS on success. */ -PJ_DECL(pj_status_t) pjmedia_jbuf_enable_discard(pjmedia_jbuf *jb, - pj_bool_t enable); +PJ_DECL(pj_status_t) pjmedia_jbuf_set_discard(pjmedia_jbuf *jb, + pjmedia_jb_discard_algo algo); /** diff --git a/pjmedia/include/pjmedia/session.h b/pjmedia/include/pjmedia/session.h index efab853a..44238c56 100644 --- a/pjmedia/include/pjmedia/session.h +++ b/pjmedia/include/pjmedia/session.h @@ -231,6 +231,26 @@ PJ_DECL(pj_status_t) pjmedia_session_resume_stream(pjmedia_session *session, unsigned index, pjmedia_dir dir); +/** + * Send RTCP SDES for the session. + * + * @param session The media session. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) +pjmedia_session_send_rtcp_sdes( const pjmedia_session *session ); + +/** + * Send RTCP BYE for the session. + * + * @param session The media session. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) +pjmedia_session_send_rtcp_bye( const pjmedia_session *session ); + /** * Enumerate media streams in the session. * diff --git a/pjmedia/include/pjmedia/stream.h b/pjmedia/include/pjmedia/stream.h index ea3a1b64..88e5a882 100644 --- a/pjmedia/include/pjmedia/stream.h +++ b/pjmedia/include/pjmedia/stream.h @@ -135,6 +135,9 @@ typedef struct pjmedia_stream_info (see #PJMEDIA_STREAM_ENABLE_KA) is enabled? */ #endif + pj_bool_t rtcp_sdes_bye_disabled; + /**< Disable automatic sending of RTCP + SDES and BYE. */ } pjmedia_stream_info; @@ -403,6 +406,26 @@ pjmedia_stream_set_dtmf_callback(pjmedia_stream *stream, void *user_data); +/** + * Send RTCP SDES for the media stream. + * + * @param stream The media stream. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) +pjmedia_stream_send_rtcp_sdes( pjmedia_stream *stream ); + +/** + * Send RTCP BYE for the media stream. + * + * @param stream The media stream. + * + * @return PJ_SUCCESS on success. + */ +PJ_DECL(pj_status_t) +pjmedia_stream_send_rtcp_bye( pjmedia_stream *stream ); + /** * @} */ diff --git a/pjmedia/include/pjmedia/transport_adapter_sample.h b/pjmedia/include/pjmedia/transport_adapter_sample.h index 6c41a69d..62eb1c0f 100644 --- a/pjmedia/include/pjmedia/transport_adapter_sample.h +++ b/pjmedia/include/pjmedia/transport_adapter_sample.h @@ -49,15 +49,18 @@ PJ_BEGIN_DECL * @param endpt The media endpoint. * @param name Optional name to identify this media transport * for logging purposes. - * @param transport The underlying media transport to send and receive - * RTP/RTCP packets. + * @param base_tp The base/underlying media transport to send and + * receive RTP/RTCP packets. + * @param del_base Specify whether the base transport should also be + * destroyed when destroy() is called upon us. * @param p_tp Pointer to receive the media transport instance. * * @return PJ_SUCCESS on success, or the appropriate error code. */ PJ_DECL(pj_status_t) pjmedia_tp_adapter_create( pjmedia_endpt *endpt, const char *name, - pjmedia_transport *transport, + pjmedia_transport *base_tp, + pj_bool_t del_base, pjmedia_transport **p_tp); PJ_END_DECL diff --git a/pjmedia/src/pjmedia-audiodev/audiodev.c b/pjmedia/src/pjmedia-audiodev/audiodev.c index 437ee584..067fc78f 100644 --- a/pjmedia/src/pjmedia-audiodev/audiodev.c +++ b/pjmedia/src/pjmedia-audiodev/audiodev.c @@ -490,11 +490,13 @@ PJ_DEF(pj_status_t) pjmedia_aud_subsys_shutdown(void) } --aud_subsys.init_count; - for (i=0; idata, len); } else { enum {NO_DATA_FT = 15 }; - pj_uint8_t amr_header = 4 || (NO_DATA_FT << 3); + pj_uint8_t amr_header = 4 | (NO_DATA_FT << 3); buf.iBuffer.Append(amr_header); } @@ -1095,7 +1095,7 @@ static void PlayCb(TAPSCommBuffer &buf, void *user_data) } else { /* PJMEDIA_FRAME_TYPE_NONE */ enum {NO_DATA_FT = 15 }; - pj_uint8_t amr_header = 4 || (NO_DATA_FT << 3); + pj_uint8_t amr_header = 4 | (NO_DATA_FT << 3); buf.iBuffer.Append(amr_header); diff --git a/pjmedia/src/pjmedia-audiodev/symb_mda_dev.cpp b/pjmedia/src/pjmedia-audiodev/symb_mda_dev.cpp index d0e66bdc..645ed394 100644 --- a/pjmedia/src/pjmedia-audiodev/symb_mda_dev.cpp +++ b/pjmedia/src/pjmedia-audiodev/symb_mda_dev.cpp @@ -239,6 +239,7 @@ private: TPtr8 iFramePtr_; TInt lastError_; pj_uint32_t timeStamp_; + CActiveSchedulerWait startAsw_; // cache variable // to avoid calculating frame length repeatedly @@ -363,6 +364,13 @@ pj_status_t CPjAudioInputEngine::StartRecord() lastError_ = KRequestPending; iInputStream_->Open(&iStreamSettings); +#if defined(PJMEDIA_AUDIO_DEV_MDA_USE_SYNC_START) && \ + PJMEDIA_AUDIO_DEV_MDA_USE_SYNC_START != 0 + + startAsw_.Start(); + +#endif + // Success PJ_LOG(4,(THIS_FILE, "Sound capture started.")); return PJ_SUCCESS; @@ -386,6 +394,10 @@ void CPjAudioInputEngine::Stop() iInputStream_ = NULL; } + if (startAsw_.IsStarted()) { + startAsw_.AsyncStop(); + } + state_ = STATE_INACTIVE; } @@ -399,12 +411,25 @@ TPtr8 & CPjAudioInputEngine::GetFrame() void CPjAudioInputEngine::MaiscOpenComplete(TInt aError) { + if (startAsw_.IsStarted()) { + startAsw_.AsyncStop(); + } + lastError_ = aError; if (aError != KErrNone) { snd_perror("Error in MaiscOpenComplete()", aError); return; } + /* Apply input volume setting if specified */ + if (parentStrm_->param.flags & + PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING) + { + stream_set_cap(&parentStrm_->base, + PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, + &parentStrm_->param.input_vol); + } + // set stream priority to normal and time sensitive iInputStream_->SetPriority(EPriorityNormal, EMdaPriorityPreferenceTime); @@ -414,7 +439,12 @@ void CPjAudioInputEngine::MaiscOpenComplete(TInt aError) TRAPD(err2, iInputStream_->ReadL(frm)); if (err2) { PJ_LOG(4,(THIS_FILE, "Exception in iInputStream_->ReadL()")); + lastError_ = err2; + return; } + + // input stream opened succesfully, set status to Active + state_ = STATE_ACTIVE; } void CPjAudioInputEngine::MaiscBufferCopied(TInt aError, @@ -547,6 +577,7 @@ private: TPtrC8 frame_; TInt lastError_; unsigned timestamp_; + CActiveSchedulerWait startAsw_; CPjAudioOutputEngine(struct mda_stream *parent_strm, pjmedia_aud_play_cb play_cb, @@ -638,6 +669,13 @@ pj_status_t CPjAudioOutputEngine::StartPlay() // Open stream. lastError_ = KRequestPending; iOutputStream_->Open(&iStreamSettings); + +#if defined(PJMEDIA_AUDIO_DEV_MDA_USE_SYNC_START) && \ + PJMEDIA_AUDIO_DEV_MDA_USE_SYNC_START != 0 + + startAsw_.Start(); + +#endif // Success PJ_LOG(4,(THIS_FILE, "Sound playback started")); @@ -662,17 +700,22 @@ void CPjAudioOutputEngine::Stop() iOutputStream_ = NULL; } + if (startAsw_.IsStarted()) { + startAsw_.AsyncStop(); + } + state_ = STATE_INACTIVE; } void CPjAudioOutputEngine::MaoscOpenComplete(TInt aError) { + if (startAsw_.IsStarted()) { + startAsw_.AsyncStop(); + } + lastError_ = aError; if (aError==KErrNone) { - // output stream opened succesfully, set status to Active - state_ = STATE_ACTIVE; - // set stream properties, 16bit 8KHz mono TMdaAudioDataSettings iSettings; iSettings.iChannels = @@ -683,8 +726,17 @@ void CPjAudioOutputEngine::MaoscOpenComplete(TInt aError) iOutputStream_->SetAudioPropertiesL(iSettings.iSampleRate, iSettings.iChannels); - // set volume to 1/2th of stream max volume - iOutputStream_->SetVolume(iOutputStream_->MaxVolume()/2); + /* Apply output volume setting if specified */ + if (parentStrm_->param.flags & + PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) + { + stream_set_cap(&parentStrm_->base, + PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, + &parentStrm_->param.output_vol); + } else { + // set volume to 1/2th of stream max volume + iOutputStream_->SetVolume(iOutputStream_->MaxVolume()/2); + } // set stream priority to normal and time sensitive iOutputStream_->SetPriority(EPriorityNormal, @@ -718,6 +770,9 @@ void CPjAudioOutputEngine::MaoscOpenComplete(TInt aError) // until whole data buffer is written. frame_.Set(frameBuf_, frameBufSize_); iOutputStream_->WriteL(frame_); + + // output stream opened succesfully, set status to Active + state_ = STATE_ACTIVE; } else { snd_perror("Error in MaoscOpenComplete()", aError); } @@ -881,7 +936,8 @@ static pj_status_t factory_default_param(pjmedia_aud_dev_factory *f, param->channel_count = 1; param->samples_per_frame = af->dev_info.default_samples_per_sec * 20 / 1000; param->bits_per_sample = BITS_PER_SAMPLE; - param->flags = af->dev_info.caps; + // Don't set the flags without specifying the flags value. + //param->flags = af->dev_info.caps; return PJ_SUCCESS; } @@ -956,6 +1012,20 @@ static pj_status_t stream_get_param(pjmedia_aud_stream *s, pj_memcpy(pi, &strm->param, sizeof(*pi)); + /* Update the output volume setting */ + if (stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, + &pi->output_vol) == PJ_SUCCESS) + { + pi->flags |= PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING; + } + + /* Update the input volume setting */ + if (stream_get_cap(s, PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING, + &pi->input_vol) == PJ_SUCCESS) + { + pi->flags |= PJMEDIA_AUD_DEV_CAP_INPUT_VOLUME_SETTING; + } + return PJ_SUCCESS; } @@ -1034,7 +1104,7 @@ static pj_status_t stream_set_cap(pjmedia_aud_stream *s, } break; case PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING: - if (strm->param.dir & PJMEDIA_DIR_CAPTURE) { + if (strm->param.dir & PJMEDIA_DIR_PLAYBACK) { PJ_ASSERT_RETURN(strm->out_engine, PJ_EINVAL); TInt max_vol = strm->out_engine->GetMaxVolume(); diff --git a/pjmedia/src/pjmedia-audiodev/symb_vas_dev.cpp b/pjmedia/src/pjmedia-audiodev/symb_vas_dev.cpp index fd4d0806..bcd9d75a 100644 --- a/pjmedia/src/pjmedia-audiodev/symb_vas_dev.cpp +++ b/pjmedia/src/pjmedia-audiodev/symb_vas_dev.cpp @@ -1130,7 +1130,7 @@ static void PlayCb(CVoIPDataBuffer *buf, void *user_data) buffer.Append((TUint8*)sf->data, len); } else { enum {NO_DATA_FT = 15 }; - pj_uint8_t amr_header = 4 || (NO_DATA_FT << 3); + pj_uint8_t amr_header = 4 | (NO_DATA_FT << 3); buffer.Append(amr_header); } @@ -1139,7 +1139,7 @@ static void PlayCb(CVoIPDataBuffer *buf, void *user_data) } else { /* PJMEDIA_FRAME_TYPE_NONE */ enum {NO_DATA_FT = 15 }; - pj_uint8_t amr_header = 4 || (NO_DATA_FT << 3); + pj_uint8_t amr_header = 4 | (NO_DATA_FT << 3); buffer.Append(amr_header); @@ -1746,12 +1746,6 @@ static pj_status_t factory_create_stream(pjmedia_aud_dev_factory *f, return PJ_RETURN_OS_ERROR(err); } - /* Apply output volume setting if specified */ - if (param->flags & PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) { - stream_set_cap(&strm->base, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, - ¶m->output_vol); - } - /* Done */ strm->base.op = &stream_op; *p_aud_strm = &strm->base; @@ -1945,10 +1939,21 @@ static pj_status_t stream_start(pjmedia_aud_stream *strm) } while (!stream->engine->IsStarted() && (now.MicroSecondsFrom(start) < VAS_WAIT_START * 1000)); - if (stream->engine->IsStarted()) + if (stream->engine->IsStarted()) { + + /* Apply output volume setting if specified */ + if (stream->param.flags & + PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING) + { + stream_set_cap(strm, + PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME_SETTING, + &stream->param.output_vol); + } + return PJ_SUCCESS; - else + } else { return PJ_ETIMEDOUT; + } } return PJ_EINVALIDOP; diff --git a/pjmedia/src/pjmedia-codec/audio_codecs.c b/pjmedia/src/pjmedia-codec/audio_codecs.c index a9e0700a..eea9ba74 100644 --- a/pjmedia/src/pjmedia-codec/audio_codecs.c +++ b/pjmedia/src/pjmedia-codec/audio_codecs.c @@ -107,6 +107,13 @@ pjmedia_codec_register_audio_codecs(pjmedia_endpt *endpt, return status; #endif /* PJMEDIA_HAS_L16_CODEC */ +#if PJMEDIA_HAS_OPENCORE_AMRNB_CODEC + /* Register OpenCORE AMR-NB */ + status = pjmedia_codec_opencore_amrnb_init(endpt); + if (status != PJ_SUCCESS) + return status; +#endif + return PJ_SUCCESS; } diff --git a/pjmedia/src/pjmedia-codec/ipp_codecs.c b/pjmedia/src/pjmedia-codec/ipp_codecs.c index f5690eee..a2ead05b 100644 --- a/pjmedia/src/pjmedia-codec/ipp_codecs.c +++ b/pjmedia/src/pjmedia-codec/ipp_codecs.c @@ -42,6 +42,7 @@ #define THIS_FILE "ipp_codecs.c" + /* Prototypes for IPP codecs factory */ static pj_status_t ipp_test_alloc( pjmedia_codec_factory *factory, const pjmedia_codec_info *id ); @@ -238,9 +239,8 @@ static struct ipp_codec { ipp_codec[] = { # if PJMEDIA_HAS_INTEL_IPP_CODEC_AMR - /* AMR-NB SID seems to produce noise, so let's just disable its VAD. */ {1, "AMR", PJMEDIA_RTP_PT_AMR, &USC_GSMAMR_Fxns, 8000, 1, 160, - 7400, 12200, 2, 0, 1, + 7400, 12200, 2, 1, 1, &predecode_amr, &parse_amr, &pack_amr, {1, {{{"octet-align", 11}, {"1", 1}}} } }, @@ -488,9 +488,7 @@ static void predecode_amr( ipp_private_t *codec_data, } else if (frame.size == 5) { /* SID */ if (info->good_quality) { - pj_bool_t STI; - STI = (((pj_uint8_t*)frame.buf)[35 >> 3] & 0x10) != 0; - usc_frame->frametype = STI? 2 : 1; + usc_frame->frametype = info->STI? 2 : 1; } else { usc_frame->frametype = setting->amr_nb ? 6 : 7; } @@ -534,6 +532,7 @@ static pj_status_t pack_amr(ipp_private_t *codec_data, void *pkt, info->frame_type = (pj_uint8_t)(info_ & 0x0F); info->good_quality = (pj_uint8_t)((info_ & 0x80) == 0); info->mode = (pj_int8_t) ((info_ >> 8) & 0x0F); + info->STI = (pj_uint8_t)((info_ >> 5) & 1); frames[nframes].buf = r + 2; frames[nframes].size = info->frame_type <= SID_FT ? @@ -1419,6 +1418,7 @@ static pj_status_t ipp_codec_encode( pjmedia_codec *codec, /* Two octets for AMR frame info, 0=LSB: * bit 0-3 : frame type + * bit 5 : STI flag * bit 6 : last frame flag * bit 7 : quality flag * bit 8-11 : mode @@ -1442,6 +1442,9 @@ static pj_status_t ipp_codec_encode( pjmedia_codec *codec, /* Quality */ if (out.frametype == 6 || out.frametype == 7) *info |= 0x80; + /* STI */ + if (out.frametype != 1) + *info |= 0x20; } else { /* Untransmited */ *info = 15; diff --git a/pjmedia/src/pjmedia-codec/opencore_amrnb.c b/pjmedia/src/pjmedia-codec/opencore_amrnb.c new file mode 100644 index 00000000..eba53215 --- /dev/null +++ b/pjmedia/src/pjmedia-codec/opencore_amrnb.c @@ -0,0 +1,820 @@ +/* $Id */ +/* + * Copyright (C) 2011 Teluu Inc. (http://www.teluu.com) + * Copyright (C) 2011 Dan Arrhenius + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * AMR-NB codec implementation with OpenCORE AMRNB library + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(PJMEDIA_HAS_OPENCORE_AMRNB_CODEC) && \ + (PJMEDIA_HAS_OPENCORE_AMRNB_CODEC != 0) + +#include +#include +#include +#include + +#define THIS_FILE "opencore_amrnb.c" + +/* Tracing */ +#define PJ_TRACE 0 + +#if PJ_TRACE +# define TRACE_(expr) PJ_LOG(4,expr) +#else +# define TRACE_(expr) +#endif + +/* Use PJMEDIA PLC */ +#define USE_PJMEDIA_PLC 1 + + + +/* Prototypes for AMR-NB factory */ +static pj_status_t amr_test_alloc(pjmedia_codec_factory *factory, + const pjmedia_codec_info *id ); +static pj_status_t amr_default_attr(pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec_param *attr ); +static pj_status_t amr_enum_codecs(pjmedia_codec_factory *factory, + unsigned *count, + pjmedia_codec_info codecs[]); +static pj_status_t amr_alloc_codec(pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec **p_codec); +static pj_status_t amr_dealloc_codec(pjmedia_codec_factory *factory, + pjmedia_codec *codec ); + +/* Prototypes for AMR-NB implementation. */ +static pj_status_t amr_codec_init(pjmedia_codec *codec, + pj_pool_t *pool ); +static pj_status_t amr_codec_open(pjmedia_codec *codec, + pjmedia_codec_param *attr ); +static pj_status_t amr_codec_close(pjmedia_codec *codec ); +static pj_status_t amr_codec_modify(pjmedia_codec *codec, + const pjmedia_codec_param *attr ); +static pj_status_t amr_codec_parse(pjmedia_codec *codec, + void *pkt, + pj_size_t pkt_size, + const pj_timestamp *ts, + unsigned *frame_cnt, + pjmedia_frame frames[]); +static pj_status_t amr_codec_encode(pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output); +static pj_status_t amr_codec_decode(pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output); +static pj_status_t amr_codec_recover(pjmedia_codec *codec, + unsigned output_buf_len, + struct pjmedia_frame *output); + + + +/* Definition for AMR-NB codec operations. */ +static pjmedia_codec_op amr_op = +{ + &amr_codec_init, + &amr_codec_open, + &amr_codec_close, + &amr_codec_modify, + &amr_codec_parse, + &amr_codec_encode, + &amr_codec_decode, + &amr_codec_recover +}; + +/* Definition for AMR-NB codec factory operations. */ +static pjmedia_codec_factory_op amr_factory_op = +{ + &amr_test_alloc, + &amr_default_attr, + &amr_enum_codecs, + &amr_alloc_codec, + &amr_dealloc_codec +}; + + +/* AMR-NB factory */ +static struct amr_codec_factory +{ + pjmedia_codec_factory base; + pjmedia_endpt *endpt; + pj_pool_t *pool; +} amr_codec_factory; + + +/* AMR-NB codec private data. */ +struct amr_data +{ + pj_pool_t *pool; + void *encoder; + void *decoder; + pj_bool_t plc_enabled; + pj_bool_t vad_enabled; + int enc_mode; + pjmedia_codec_amr_pack_setting enc_setting; + pjmedia_codec_amr_pack_setting dec_setting; +#if USE_PJMEDIA_PLC + pjmedia_plc *plc; +#endif + pj_timestamp last_tx; +}; + +static pjmedia_codec_amrnb_config def_config = +{ + PJ_FALSE, /* octet align */ + 5900 /* bitrate */ +}; + + + +/* + * Initialize and register AMR-NB codec factory to pjmedia endpoint. + */ +PJ_DEF(pj_status_t) pjmedia_codec_opencore_amrnb_init( pjmedia_endpt *endpt ) +{ + pjmedia_codec_mgr *codec_mgr; + pj_status_t status; + + if (amr_codec_factory.pool != NULL) + return PJ_SUCCESS; + + /* Create AMR-NB codec factory. */ + amr_codec_factory.base.op = &amr_factory_op; + amr_codec_factory.base.factory_data = NULL; + amr_codec_factory.endpt = endpt; + + amr_codec_factory.pool = pjmedia_endpt_create_pool(endpt, "amrnb", 1000, + 1000); + if (!amr_codec_factory.pool) + return PJ_ENOMEM; + + /* Get the codec manager. */ + codec_mgr = pjmedia_endpt_get_codec_mgr(endpt); + if (!codec_mgr) { + status = PJ_EINVALIDOP; + goto on_error; + } + + /* Register codec factory to endpoint. */ + status = pjmedia_codec_mgr_register_factory(codec_mgr, + &amr_codec_factory.base); + if (status != PJ_SUCCESS) + goto on_error; + + /* Done. */ + return PJ_SUCCESS; + +on_error: + pj_pool_release(amr_codec_factory.pool); + amr_codec_factory.pool = NULL; + return status; +} + + +/* + * Unregister AMR-NB codec factory from pjmedia endpoint and deinitialize + * the AMR-NB codec library. + */ +PJ_DEF(pj_status_t) pjmedia_codec_opencore_amrnb_deinit(void) +{ + pjmedia_codec_mgr *codec_mgr; + pj_status_t status; + + if (amr_codec_factory.pool == NULL) + return PJ_SUCCESS; + + /* Get the codec manager. */ + codec_mgr = pjmedia_endpt_get_codec_mgr(amr_codec_factory.endpt); + if (!codec_mgr) { + pj_pool_release(amr_codec_factory.pool); + amr_codec_factory.pool = NULL; + return PJ_EINVALIDOP; + } + + /* Unregister AMR-NB codec factory. */ + status = pjmedia_codec_mgr_unregister_factory(codec_mgr, + &amr_codec_factory.base); + + /* Destroy pool. */ + pj_pool_release(amr_codec_factory.pool); + amr_codec_factory.pool = NULL; + + return status; +} + + +PJ_DEF(pj_status_t) pjmedia_codec_opencore_amrnb_set_config( + const pjmedia_codec_amrnb_config *config) +{ + unsigned nbitrates; + + + def_config = *config; + + /* Normalize bitrate. */ + nbitrates = PJ_ARRAY_SIZE(pjmedia_codec_amrnb_bitrates); + if (def_config.bitrate < pjmedia_codec_amrnb_bitrates[0]) + def_config.bitrate = pjmedia_codec_amrnb_bitrates[0]; + else if (def_config.bitrate > pjmedia_codec_amrnb_bitrates[nbitrates-1]) + def_config.bitrate = pjmedia_codec_amrnb_bitrates[nbitrates-1]; + else + { + unsigned i; + + for (i = 0; i < nbitrates; ++i) { + if (def_config.bitrate <= pjmedia_codec_amrnb_bitrates[i]) + break; + } + def_config.bitrate = pjmedia_codec_amrnb_bitrates[i]; + } + + return PJ_SUCCESS; +} + +/* + * Check if factory can allocate the specified codec. + */ +static pj_status_t amr_test_alloc( pjmedia_codec_factory *factory, + const pjmedia_codec_info *info ) +{ + PJ_UNUSED_ARG(factory); + + /* Check payload type. */ + if (info->pt != PJMEDIA_RTP_PT_AMR) + return PJMEDIA_CODEC_EUNSUP; + + /* Ignore the rest, since it's static payload type. */ + + return PJ_SUCCESS; +} + +/* + * Generate default attribute. + */ +static pj_status_t amr_default_attr( pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec_param *attr ) +{ + PJ_UNUSED_ARG(factory); + PJ_UNUSED_ARG(id); + + pj_bzero(attr, sizeof(pjmedia_codec_param)); + attr->info.clock_rate = 8000; + attr->info.channel_cnt = 1; + attr->info.avg_bps = def_config.bitrate; + attr->info.max_bps = pjmedia_codec_amrnb_bitrates[7]; + attr->info.pcm_bits_per_sample = 16; + attr->info.frm_ptime = 20; + attr->info.pt = PJMEDIA_RTP_PT_AMR; + + attr->setting.frm_per_pkt = 2; + attr->setting.vad = 1; + attr->setting.plc = 1; + + if (def_config.octet_align) { + attr->setting.dec_fmtp.cnt = 1; + attr->setting.dec_fmtp.param[0].name = pj_str("octet-align"); + attr->setting.dec_fmtp.param[0].val = pj_str("1"); + } + + /* Default all other flag bits disabled. */ + + return PJ_SUCCESS; +} + + +/* + * Enum codecs supported by this factory (i.e. only AMR-NB!). + */ +static pj_status_t amr_enum_codecs( pjmedia_codec_factory *factory, + unsigned *count, + pjmedia_codec_info codecs[]) +{ + PJ_UNUSED_ARG(factory); + PJ_ASSERT_RETURN(codecs && *count > 0, PJ_EINVAL); + + pj_bzero(&codecs[0], sizeof(pjmedia_codec_info)); + codecs[0].encoding_name = pj_str("AMR"); + codecs[0].pt = PJMEDIA_RTP_PT_AMR; + codecs[0].type = PJMEDIA_TYPE_AUDIO; + codecs[0].clock_rate = 8000; + codecs[0].channel_cnt = 1; + + *count = 1; + + return PJ_SUCCESS; +} + + +/* + * Allocate a new AMR-NB codec instance. + */ +static pj_status_t amr_alloc_codec( pjmedia_codec_factory *factory, + const pjmedia_codec_info *id, + pjmedia_codec **p_codec) +{ + pj_pool_t *pool; + pjmedia_codec *codec; + struct amr_data *amr_data; + pj_status_t status; + + PJ_ASSERT_RETURN(factory && id && p_codec, PJ_EINVAL); + PJ_ASSERT_RETURN(factory == &amr_codec_factory.base, PJ_EINVAL); + + pool = pjmedia_endpt_create_pool(amr_codec_factory.endpt, "amrnb-inst", + 512, 512); + + codec = PJ_POOL_ZALLOC_T(pool, pjmedia_codec); + PJ_ASSERT_RETURN(codec != NULL, PJ_ENOMEM); + codec->op = &amr_op; + codec->factory = factory; + + amr_data = PJ_POOL_ZALLOC_T(pool, struct amr_data); + codec->codec_data = amr_data; + amr_data->pool = pool; + +#if USE_PJMEDIA_PLC + /* Create PLC */ + status = pjmedia_plc_create(pool, 8000, 160, 0, &amr_data->plc); + if (status != PJ_SUCCESS) { + return status; + } +#else + PJ_UNUSED_ARG(status); +#endif + *p_codec = codec; + return PJ_SUCCESS; +} + + +/* + * Free codec. + */ +static pj_status_t amr_dealloc_codec( pjmedia_codec_factory *factory, + pjmedia_codec *codec ) +{ + struct amr_data *amr_data; + + PJ_ASSERT_RETURN(factory && codec, PJ_EINVAL); + PJ_ASSERT_RETURN(factory == &amr_codec_factory.base, PJ_EINVAL); + + amr_data = (struct amr_data*) codec->codec_data; + + /* Close codec, if it's not closed. */ + amr_codec_close(codec); + + pj_pool_release(amr_data->pool); + amr_data = NULL; + + return PJ_SUCCESS; +} + +/* + * Init codec. + */ +static pj_status_t amr_codec_init( pjmedia_codec *codec, + pj_pool_t *pool ) +{ + PJ_UNUSED_ARG(codec); + PJ_UNUSED_ARG(pool); + return PJ_SUCCESS; +} + + +/* + * Open codec. + */ +static pj_status_t amr_codec_open( pjmedia_codec *codec, + pjmedia_codec_param *attr ) +{ + struct amr_data *amr_data = (struct amr_data*) codec->codec_data; + pjmedia_codec_amr_pack_setting *setting; + unsigned i; + pj_uint8_t octet_align = 0; + pj_int8_t enc_mode; + const pj_str_t STR_FMTP_OCTET_ALIGN = {"octet-align", 11}; + + PJ_ASSERT_RETURN(codec && attr, PJ_EINVAL); + PJ_ASSERT_RETURN(amr_data != NULL, PJ_EINVALIDOP); + + enc_mode = pjmedia_codec_amr_get_mode(attr->info.avg_bps); + pj_assert(enc_mode >= 0 && enc_mode <= 7); + + /* Check octet-align */ + for (i = 0; i < attr->setting.dec_fmtp.cnt; ++i) { + if (pj_stricmp(&attr->setting.dec_fmtp.param[i].name, + &STR_FMTP_OCTET_ALIGN) == 0) + { + octet_align = (pj_uint8_t) + (pj_strtoul(&attr->setting.dec_fmtp.param[i].val)); + break; + } + } + + /* Check mode-set */ + for (i = 0; i < attr->setting.enc_fmtp.cnt; ++i) { + const pj_str_t STR_FMTP_MODE_SET = {"mode-set", 8}; + + if (pj_stricmp(&attr->setting.enc_fmtp.param[i].name, + &STR_FMTP_MODE_SET) == 0) + { + const char *p; + pj_size_t l; + pj_int8_t diff = 99; + + /* Encoding mode is chosen based on local default mode setting: + * - if local default mode is included in the mode-set, use it + * - otherwise, find the closest mode to local default mode; + * if there are two closest modes, prefer to use the higher + * one, e.g: local default mode is 4, the mode-set param + * contains '2,3,5,6', then 5 will be chosen. + */ + p = pj_strbuf(&attr->setting.enc_fmtp.param[i].val); + l = pj_strlen(&attr->setting.enc_fmtp.param[i].val); + while (l--) { + if (*p>='0' && *p<='7') { + pj_int8_t tmp = *p - '0' - enc_mode; + + if (PJ_ABS(diff) > PJ_ABS(tmp) || + (PJ_ABS(diff) == PJ_ABS(tmp) && tmp > diff)) + { + diff = tmp; + if (diff == 0) break; + } + } + ++p; + } + PJ_ASSERT_RETURN(diff != 99, PJMEDIA_CODEC_EFAILED); + + enc_mode = enc_mode + diff; + + break; + } + } + + amr_data->vad_enabled = (attr->setting.vad != 0); + amr_data->plc_enabled = (attr->setting.plc != 0); + amr_data->enc_mode = enc_mode; + + amr_data->encoder = Encoder_Interface_init(amr_data->vad_enabled); + if (amr_data->encoder == NULL) { + TRACE_((THIS_FILE, "Encoder_Interface_init() failed")); + amr_codec_close(codec); + return PJMEDIA_CODEC_EFAILED; + } + setting = &amr_data->enc_setting; + pj_bzero(setting, sizeof(pjmedia_codec_amr_pack_setting)); + setting->amr_nb = 1; + setting->reorder = 0; + setting->octet_aligned = octet_align; + setting->cmr = 15; + + amr_data->decoder = Decoder_Interface_init(); + if (amr_data->decoder == NULL) { + TRACE_((THIS_FILE, "Decoder_Interface_init() failed")); + amr_codec_close(codec); + return PJMEDIA_CODEC_EFAILED; + } + setting = &amr_data->dec_setting; + pj_bzero(setting, sizeof(pjmedia_codec_amr_pack_setting)); + setting->amr_nb = 1; + setting->reorder = 0; + setting->octet_aligned = octet_align; + + TRACE_((THIS_FILE, "AMR-NB codec allocated: vad=%d, plc=%d, bitrate=%d", + amr_data->vad_enabled, amr_data->plc_enabled, + pjmedia_codec_amrnb_bitrates[amr_data->enc_mode])); + return PJ_SUCCESS; +} + + +/* + * Close codec. + */ +static pj_status_t amr_codec_close( pjmedia_codec *codec ) +{ + struct amr_data *amr_data; + + PJ_ASSERT_RETURN(codec, PJ_EINVAL); + + amr_data = (struct amr_data*) codec->codec_data; + PJ_ASSERT_RETURN(amr_data != NULL, PJ_EINVALIDOP); + + if (amr_data->encoder) { + Encoder_Interface_exit(amr_data->encoder); + amr_data->encoder = NULL; + } + + if (amr_data->decoder) { + Decoder_Interface_exit(amr_data->decoder); + amr_data->decoder = NULL; + } + + TRACE_((THIS_FILE, "AMR-NB codec closed")); + return PJ_SUCCESS; +} + + +/* + * Modify codec settings. + */ +static pj_status_t amr_codec_modify( pjmedia_codec *codec, + const pjmedia_codec_param *attr ) +{ + struct amr_data *amr_data = (struct amr_data*) codec->codec_data; + pj_bool_t prev_vad_state; + + pj_assert(amr_data != NULL); + pj_assert(amr_data->encoder != NULL && amr_data->decoder != NULL); + + prev_vad_state = amr_data->vad_enabled; + amr_data->vad_enabled = (attr->setting.vad != 0); + amr_data->plc_enabled = (attr->setting.plc != 0); + + if (prev_vad_state != amr_data->vad_enabled) { + /* Reinit AMR encoder to update VAD setting */ + TRACE_((THIS_FILE, "Reiniting AMR encoder to update VAD setting.")); + Encoder_Interface_exit(amr_data->encoder); + amr_data->encoder = Encoder_Interface_init(amr_data->vad_enabled); + if (amr_data->encoder == NULL) { + TRACE_((THIS_FILE, "Encoder_Interface_init() failed")); + amr_codec_close(codec); + return PJMEDIA_CODEC_EFAILED; + } + } + + TRACE_((THIS_FILE, "AMR-NB codec modified: vad=%d, plc=%d", + amr_data->vad_enabled, amr_data->plc_enabled)); + return PJ_SUCCESS; +} + + +/* + * Get frames in the packet. + */ +static pj_status_t amr_codec_parse( pjmedia_codec *codec, + void *pkt, + pj_size_t pkt_size, + const pj_timestamp *ts, + unsigned *frame_cnt, + pjmedia_frame frames[]) +{ + struct amr_data *amr_data = (struct amr_data*) codec->codec_data; + pj_uint8_t cmr; + pj_status_t status; + + status = pjmedia_codec_amr_parse(pkt, pkt_size, ts, &amr_data->dec_setting, + frames, frame_cnt, &cmr); + if (status != PJ_SUCCESS) + return status; + + /* Check for Change Mode Request. */ + if (cmr <= 7 && amr_data->enc_mode != cmr) { + amr_data->enc_mode = cmr; + TRACE_((THIS_FILE, "AMR-NB encoder switched mode to %d (%dbps)", + amr_data->enc_mode, + pjmedia_codec_amrnb_bitrates[amr_data->enc_mode])); + } + + return PJ_SUCCESS; +} + + +/* + * Encode frame. + */ +static pj_status_t amr_codec_encode( pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output) +{ + struct amr_data *amr_data = (struct amr_data*) codec->codec_data; + unsigned char *bitstream; + pj_int16_t *speech; + unsigned nsamples, samples_per_frame; + enum {MAX_FRAMES_PER_PACKET = 16}; + pjmedia_frame frames[MAX_FRAMES_PER_PACKET]; + pj_uint8_t *p; + unsigned i, out_size = 0, nframes = 0; + pj_size_t payload_len; + unsigned dtx_cnt, sid_cnt; + pj_status_t status; + int size; + + pj_assert(amr_data != NULL); + PJ_ASSERT_RETURN(input && output, PJ_EINVAL); + + nsamples = input->size >> 1; + samples_per_frame = 160; + PJ_ASSERT_RETURN(nsamples % samples_per_frame == 0, + PJMEDIA_CODEC_EPCMFRMINLEN); + + nframes = nsamples / samples_per_frame; + PJ_ASSERT_RETURN(nframes <= MAX_FRAMES_PER_PACKET, + PJMEDIA_CODEC_EFRMTOOSHORT); + + /* Encode the frames */ + speech = (pj_int16_t*)input->buf; + bitstream = (unsigned char*)output->buf; + while (nsamples >= samples_per_frame) { + size = Encoder_Interface_Encode (amr_data->encoder, amr_data->enc_mode, + speech, bitstream, 0); + if (size == 0) { + output->size = 0; + output->buf = NULL; + output->type = PJMEDIA_FRAME_TYPE_NONE; + TRACE_((THIS_FILE, "AMR-NB encode() failed")); + return PJMEDIA_CODEC_EFAILED; + } + nsamples -= 160; + speech += samples_per_frame; + bitstream += size; + out_size += size; + TRACE_((THIS_FILE, "AMR-NB encode(): mode=%d, size=%d", + amr_data->enc_mode, out_size)); + } + + /* Pack payload */ + p = (pj_uint8_t*)output->buf + output_buf_len - out_size; + pj_memmove(p, output->buf, out_size); + dtx_cnt = sid_cnt = 0; + for (i = 0; i < nframes; ++i) { + pjmedia_codec_amr_bit_info *info = (pjmedia_codec_amr_bit_info*) + &frames[i].bit_info; + info->frame_type = (pj_uint8_t)((*p >> 3) & 0x0F); + info->good_quality = (pj_uint8_t)((*p >> 2) & 0x01); + info->mode = (pj_int8_t)amr_data->enc_mode; + info->start_bit = 0; + frames[i].buf = p + 1; + frames[i].size = (info->frame_type <= 8)? + pjmedia_codec_amrnb_framelen[info->frame_type] : 0; + p += frames[i].size + 1; + + /* Count the number of SID and DTX frames */ + if (info->frame_type == 15) /* DTX*/ + ++dtx_cnt; + else if (info->frame_type == 8) /* SID */ + ++sid_cnt; + } + + /* VA generates DTX frames as DTX+SID frames switching quickly and it + * seems that the SID frames occur too often (assuming the purpose is + * only for keeping NAT alive?). So let's modify the behavior a bit. + * Only an SID frame will be sent every PJMEDIA_CODEC_MAX_SILENCE_PERIOD + * milliseconds. + */ + if (sid_cnt + dtx_cnt == nframes) { + pj_int32_t dtx_duration; + + dtx_duration = pj_timestamp_diff32(&amr_data->last_tx, + &input->timestamp); + if (PJMEDIA_CODEC_MAX_SILENCE_PERIOD == -1 || + dtx_duration < PJMEDIA_CODEC_MAX_SILENCE_PERIOD*8000/1000) + { + output->size = 0; + output->type = PJMEDIA_FRAME_TYPE_NONE; + output->timestamp = input->timestamp; + return PJ_SUCCESS; + } + } + + payload_len = output_buf_len; + + status = pjmedia_codec_amr_pack(frames, nframes, &amr_data->enc_setting, + output->buf, &payload_len); + if (status != PJ_SUCCESS) { + output->size = 0; + output->buf = NULL; + output->type = PJMEDIA_FRAME_TYPE_NONE; + TRACE_((THIS_FILE, "Failed to pack AMR payload, status=%d", status)); + return status; + } + + output->size = payload_len; + output->type = PJMEDIA_FRAME_TYPE_AUDIO; + output->timestamp = input->timestamp; + + amr_data->last_tx = input->timestamp; + + return PJ_SUCCESS; +} + + +/* + * Decode frame. + */ +static pj_status_t amr_codec_decode( pjmedia_codec *codec, + const struct pjmedia_frame *input, + unsigned output_buf_len, + struct pjmedia_frame *output) +{ + struct amr_data *amr_data = (struct amr_data*) codec->codec_data; + pjmedia_frame input_; + pjmedia_codec_amr_bit_info *info; + /* VA AMR-NB decoding buffer: AMR-NB max frame size + 1 byte header. */ + unsigned char bitstream[32]; + + pj_assert(amr_data != NULL); + PJ_ASSERT_RETURN(input && output, PJ_EINVAL); + + if (output_buf_len < 320) + return PJMEDIA_CODEC_EPCMTOOSHORT; + + input_.buf = &bitstream[1]; + input_.size = 31; /* AMR-NB max frame size */ + pjmedia_codec_amr_predecode(input, &amr_data->dec_setting, &input_); + info = (pjmedia_codec_amr_bit_info*)&input_.bit_info; + + /* VA AMRNB decoder requires frame info in the first byte. */ + bitstream[0] = (info->frame_type << 3) | (info->good_quality << 2); + + TRACE_((THIS_FILE, "AMR-NB decode(): mode=%d, ft=%d, size=%d", + info->mode, info->frame_type, input_.size)); + + /* Decode */ + Decoder_Interface_Decode(amr_data->decoder, bitstream, + (pj_int16_t*)output->buf, 0); + + output->size = 320; + output->type = PJMEDIA_FRAME_TYPE_AUDIO; + output->timestamp = input->timestamp; + +#if USE_PJMEDIA_PLC + if (amr_data->plc_enabled) + pjmedia_plc_save(amr_data->plc, (pj_int16_t*)output->buf); +#endif + + return PJ_SUCCESS; +} + + +/* + * Recover lost frame. + */ +#if USE_PJMEDIA_PLC +/* + * Recover lost frame. + */ +static pj_status_t amr_codec_recover( pjmedia_codec *codec, + unsigned output_buf_len, + struct pjmedia_frame *output) +{ + struct amr_data *amr_data = codec->codec_data; + + TRACE_((THIS_FILE, "amr_codec_recover")); + + PJ_ASSERT_RETURN(amr_data->plc_enabled, PJ_EINVALIDOP); + + PJ_ASSERT_RETURN(output_buf_len >= 320, PJMEDIA_CODEC_EPCMTOOSHORT); + + pjmedia_plc_generate(amr_data->plc, (pj_int16_t*)output->buf); + + output->size = 320; + output->type = PJMEDIA_FRAME_TYPE_AUDIO; + + return PJ_SUCCESS; +} +#endif + +#if defined(_MSC_VER) && PJMEDIA_AUTO_LINK_OPENCORE_AMR_LIBS +# if PJMEDIA_OPENCORE_AMR_BUILT_WITH_GCC +# pragma comment( lib, "libopencore-amrnb.a") +# else +# error Unsupported OpenCORE AMR library, fix here +# endif +#endif + +#endif diff --git a/pjmedia/src/pjmedia-codec/passthrough.c b/pjmedia/src/pjmedia-codec/passthrough.c index 8ded8e2a..8fdd8271 100644 --- a/pjmedia/src/pjmedia-codec/passthrough.c +++ b/pjmedia/src/pjmedia-codec/passthrough.c @@ -267,6 +267,8 @@ static pj_status_t pack_amr ( codec_private_t *codec_data, } info->good_quality = 1; info->mode = setting->enc_mode; + if (info->frame_type == SID_FT) + info->STI = (sf->data[4] >> 4) & 1; frames[i].buf = sf->data; frames[i].size = len; diff --git a/pjmedia/src/pjmedia/alaw_ulaw.c b/pjmedia/src/pjmedia/alaw_ulaw.c index 7bb5c77f..0a14de87 100644 --- a/pjmedia/src/pjmedia/alaw_ulaw.c +++ b/pjmedia/src/pjmedia/alaw_ulaw.c @@ -128,6 +128,12 @@ PJ_DEF(pj_uint8_t) pjmedia_linear2alaw( } else { mask = 0x55; /* sign bit = 0 */ pcm_val = -pcm_val - 8; + + /* https://trac.pjsip.org/repos/ticket/1301 + * Thank you K Johnson - Zetron - 27 May 2011 + */ + if (pcm_val < 0) + pcm_val = 0; } /* Convert the scaled magnitude to segment number. */ diff --git a/pjmedia/src/pjmedia/delaybuf.c b/pjmedia/src/pjmedia/delaybuf.c index a54ec5de..9042ef99 100644 --- a/pjmedia/src/pjmedia/delaybuf.c +++ b/pjmedia/src/pjmedia/delaybuf.c @@ -100,9 +100,6 @@ PJ_DEF(pj_status_t) pjmedia_delay_buf_create( pj_pool_t *pool, PJ_ASSERT_RETURN(pool && samples_per_frame && clock_rate && channel_count && p_b, PJ_EINVAL); - PJ_ASSERT_RETURN(options==0, PJ_EINVAL); - - PJ_UNUSED_ARG(options); if (!name) { name = "delaybuf"; @@ -127,11 +124,16 @@ PJ_DEF(pj_status_t) pjmedia_delay_buf_create( pj_pool_t *pool, if (status != PJ_SUCCESS) return status; - /* Create WSOLA */ - status = pjmedia_wsola_create(pool, clock_rate, samples_per_frame, 1, - PJMEDIA_WSOLA_NO_FADING, &b->wsola); - if (status != PJ_SUCCESS) - return status; + if (!(options & PJMEDIA_DELAY_BUF_SIMPLE_FIFO)) { + /* Create WSOLA */ + status = pjmedia_wsola_create(pool, clock_rate, samples_per_frame, 1, + PJMEDIA_WSOLA_NO_FADING, &b->wsola); + if (status != PJ_SUCCESS) + return status; + PJ_LOG(5, (b->obj_name, "Using delay buffer with WSOLA.")); + } else { + PJ_LOG(5, (b->obj_name, "Using simple FIFO delay buffer.")); + } /* Finally, create mutex */ status = pj_lock_create_recursive_mutex(pool, b->obj_name, @@ -148,15 +150,17 @@ PJ_DEF(pj_status_t) pjmedia_delay_buf_create( pj_pool_t *pool, PJ_DEF(pj_status_t) pjmedia_delay_buf_destroy(pjmedia_delay_buf *b) { - pj_status_t status; + pj_status_t status = PJ_SUCCESS; PJ_ASSERT_RETURN(b, PJ_EINVAL); pj_lock_acquire(b->lock); - status = pjmedia_wsola_destroy(b->wsola); - if (status == PJ_SUCCESS) - b->wsola = NULL; + if (b->wsola) { + status = pjmedia_wsola_destroy(b->wsola); + if (status == PJ_SUCCESS) + b->wsola = NULL; + } pj_lock_release(b->lock); @@ -265,12 +269,14 @@ PJ_DEF(pj_status_t) pjmedia_delay_buf_put(pjmedia_delay_buf *b, pj_lock_acquire(b->lock); - update(b, OP_PUT); + if (b->wsola) { + update(b, OP_PUT); - status = pjmedia_wsola_save(b->wsola, frame, PJ_FALSE); - if (status != PJ_SUCCESS) { - pj_lock_release(b->lock); - return status; + status = pjmedia_wsola_save(b->wsola, frame, PJ_FALSE); + if (status != PJ_SUCCESS) { + pj_lock_release(b->lock); + return status; + } } /* Overflow checking */ @@ -278,13 +284,15 @@ PJ_DEF(pj_status_t) pjmedia_delay_buf_put(pjmedia_delay_buf *b, b->max_cnt) { unsigned erase_cnt; - - /* shrink one frame or just the diff? */ - //erase_cnt = b->samples_per_frame; - erase_cnt = pjmedia_circ_buf_get_len(b->circ_buf) + - b->samples_per_frame - b->max_cnt; - shrink_buffer(b, erase_cnt); + if (b->wsola) { + /* shrink one frame or just the diff? */ + //erase_cnt = b->samples_per_frame; + erase_cnt = pjmedia_circ_buf_get_len(b->circ_buf) + + b->samples_per_frame - b->max_cnt; + + shrink_buffer(b, erase_cnt); + } /* Check if shrinking failed or erased count is less than requested, * delaybuf needs to drop eldest samples, this is bad since the voice @@ -298,9 +306,9 @@ PJ_DEF(pj_status_t) pjmedia_delay_buf_put(pjmedia_delay_buf *b, pjmedia_circ_buf_adv_read_ptr(b->circ_buf, erase_cnt); - PJ_LOG(4,(b->obj_name,"Shrinking failed or insufficient, dropping" - " %d eldest samples, buf_cnt=%d", erase_cnt, - pjmedia_circ_buf_get_len(b->circ_buf))); + PJ_LOG(4,(b->obj_name,"%sDropping %d eldest samples, buf_cnt=%d", + (b->wsola? "Shrinking failed or insufficient. ": ""), + erase_cnt, pjmedia_circ_buf_get_len(b->circ_buf))); } } @@ -313,13 +321,14 @@ PJ_DEF(pj_status_t) pjmedia_delay_buf_put(pjmedia_delay_buf *b, PJ_DEF(pj_status_t) pjmedia_delay_buf_get( pjmedia_delay_buf *b, pj_int16_t frame[]) { - pj_status_t status; + pj_status_t status = PJ_SUCCESS; PJ_ASSERT_RETURN(b && frame, PJ_EINVAL); pj_lock_acquire(b->lock); - update(b, OP_GET); + if (b->wsola) + update(b, OP_GET); /* Starvation checking */ if (pjmedia_circ_buf_get_len(b->circ_buf) < b->samples_per_frame) { @@ -327,24 +336,29 @@ PJ_DEF(pj_status_t) pjmedia_delay_buf_get( pjmedia_delay_buf *b, PJ_LOG(4,(b->obj_name,"Underflow, buf_cnt=%d, will generate 1 frame", pjmedia_circ_buf_get_len(b->circ_buf))); - status = pjmedia_wsola_generate(b->wsola, frame); + if (b->wsola) { + status = pjmedia_wsola_generate(b->wsola, frame); - if (status == PJ_SUCCESS) { - TRACE__((b->obj_name,"Successfully generate 1 frame")); - if (pjmedia_circ_buf_get_len(b->circ_buf) == 0) { - pj_lock_release(b->lock); - return PJ_SUCCESS; - } + if (status == PJ_SUCCESS) { + TRACE__((b->obj_name,"Successfully generate 1 frame")); + if (pjmedia_circ_buf_get_len(b->circ_buf) == 0) { + pj_lock_release(b->lock); + return PJ_SUCCESS; + } - /* Put generated frame into buffer */ - pjmedia_circ_buf_write(b->circ_buf, frame, b->samples_per_frame); + /* Put generated frame into buffer */ + pjmedia_circ_buf_write(b->circ_buf, frame, + b->samples_per_frame); + } + } - } else { + if (!b->wsola || status != PJ_SUCCESS) { unsigned buf_len = pjmedia_circ_buf_get_len(b->circ_buf); /* Give all what delay buffer has, then pad with zeroes */ - PJ_LOG(4,(b->obj_name,"Error generating frame, status=%d", - status)); + if (b->wsola) + PJ_LOG(4,(b->obj_name,"Error generating frame, status=%d", + status)); pjmedia_circ_buf_read(b->circ_buf, frame, buf_len); pjmedia_zero_samples(&frame[buf_len], @@ -379,7 +393,8 @@ PJ_DEF(pj_status_t) pjmedia_delay_buf_reset(pjmedia_delay_buf *b) pjmedia_circ_buf_reset(b->circ_buf); /* Reset WSOLA */ - pjmedia_wsola_reset(b->wsola, 0); + if (b->wsola) + pjmedia_wsola_reset(b->wsola, 0); pj_lock_release(b->lock); diff --git a/pjmedia/src/pjmedia/echo_common.c b/pjmedia/src/pjmedia/echo_common.c index 7316dfe8..191a80f1 100644 --- a/pjmedia/src/pjmedia/echo_common.c +++ b/pjmedia/src/pjmedia/echo_common.c @@ -145,6 +145,7 @@ PJ_DEF(pj_status_t) pjmedia_echo_create2(pj_pool_t *pool, pjmedia_echo_state **p_echo ) { unsigned ptime, lat_cnt; + unsigned delay_buf_opt = 0; pjmedia_echo_state *ec; pj_status_t status; @@ -212,10 +213,12 @@ PJ_DEF(pj_status_t) pjmedia_echo_create2(pj_pool_t *pool, } /* Create delay buffer to compensate drifts */ + if (options & PJMEDIA_ECHO_USE_SIMPLE_FIFO) + delay_buf_opt |= PJMEDIA_DELAY_BUF_SIMPLE_FIFO; status = pjmedia_delay_buf_create(ec->pool, ec->obj_name, clock_rate, samples_per_frame, channel_count, (PJMEDIA_SOUND_BUFFER_COUNT+1) * ptime, - 0, &ec->delay_buf); + delay_buf_opt, &ec->delay_buf); if (status != PJ_SUCCESS) { pj_pool_release(pool); return status; diff --git a/pjmedia/src/pjmedia/jbuf.c b/pjmedia/src/pjmedia/jbuf.c index 52a3b0eb..94a88c15 100644 --- a/pjmedia/src/pjmedia/jbuf.c +++ b/pjmedia/src/pjmedia/jbuf.c @@ -32,14 +32,6 @@ #define THIS_FILE "jbuf.c" -/* Minimal difference between JB size and 2*burst-level to perform - * JB shrinking. - */ -#define SAFE_SHRINKING_DIFF 1 - -/* Minimal gap (in ms) between JB shrinking */ -#define MIN_SHRINK_GAP_MSEC 200 - /* Invalid sequence number, used as the initial value. */ #define INVALID_OFFSET -9999 @@ -54,6 +46,12 @@ #define INIT_CYCLE 10 +/* Minimal difference between JB size and 2*burst-level to perform + * JB shrinking in static discard algorithm. + */ +#define STA_DISC_SAFE_SHRINKING_DIFF 1 + + /* Struct of JB internal buffer, represented in a circular buffer containing * frame content, frame type, frame length, and frame bit info. */ @@ -82,6 +80,11 @@ typedef struct jb_framelist_t } jb_framelist_t; +typedef void (*discard_algo)(pjmedia_jbuf *jb); +static void jbuf_discard_static(pjmedia_jbuf *jb); +static void jbuf_discard_progressive(pjmedia_jbuf *jb); + + struct pjmedia_jbuf { /* Settings (consts) */ @@ -98,6 +101,7 @@ struct pjmedia_jbuf won't be included in level calculation */ int jb_min_shrink_gap; /**< How often can we shrink */ + discard_algo jb_discard_algo; /**< Discard algorithm */ /* Buffer */ jb_framelist_t jb_framelist; /**< the buffer */ @@ -120,13 +124,16 @@ struct pjmedia_jbuf operation), the value may be continuously updated based on current frame burst level. */ + pj_bool_t jb_prefetching; /**< flag if jbuf is prefetching. */ int jb_status; /**< status is 'init' until the first 'put' operation */ int jb_init_cycle_cnt; /**< status is 'init' until the first 'put' operation */ - int jb_last_del_seq; /**< Seq # of last frame deleted */ - int jb_last_discard_seq;/**< Seq # of last frame discarded */ + int jb_discard_ref; /**< Seq # of last frame deleted or + discarded */ + unsigned jb_discard_dist; /**< Distance from jb_discard_ref + to perform discard (in frm) */ /* Statistics */ pj_math_stat jb_delay; /**< Delay statistics of jitter buffer @@ -141,7 +148,6 @@ struct pjmedia_jbuf #define JB_STATUS_INITIALIZING 0 #define JB_STATUS_PROCESSING 1 -#define JB_STATUS_PREFETCHING 2 @@ -451,7 +457,7 @@ static pj_status_t jb_framelist_put_at(jb_framelist_t *framelist, enum { MAX_MISORDER = 100 }; enum { MAX_DROPOUT = 3000 }; - assert(frame_size <= framelist->frame_size); + PJ_ASSERT_RETURN(frame_size <= framelist->frame_size, PJ_EINVAL); /* too late or sequence restart */ if (index < framelist->origin) { @@ -507,15 +513,32 @@ static pj_status_t jb_framelist_put_at(jb_framelist_t *framelist, /* copy frame content */ pj_memcpy(framelist->content + pos * framelist->frame_size, frame, frame_size); - return PJ_SUCCESS; - } else { - /* frame is being discarded */ - framelist->discarded_num++; - return PJ_EIGNORED; } + + return PJ_SUCCESS; } +static pj_status_t jb_framelist_discard(jb_framelist_t *framelist, + int index) +{ + unsigned pos; + + PJ_ASSERT_RETURN(index >= framelist->origin && + index < framelist->origin + (int)framelist->size, + PJ_EINVAL); + + /* Get the slot position */ + pos = (framelist->head + (index - framelist->origin)) % + framelist->max_count; + + /* Discard the frame */ + framelist->frame_type[pos] = PJMEDIA_JB_DISCARDED_FRAME; + framelist->discarded_num++; + + return PJ_SUCCESS; +} + enum pjmedia_jb_op { @@ -548,13 +571,13 @@ PJ_DEF(pj_status_t) pjmedia_jbuf_create(pj_pool_t *pool, jb->jb_min_prefetch = 0; jb->jb_max_prefetch = max_count*4/5; jb->jb_max_count = max_count; - jb->jb_min_shrink_gap= MIN_SHRINK_GAP_MSEC / ptime; + jb->jb_min_shrink_gap= PJMEDIA_JBUF_DISC_MIN_GAP / ptime; jb->jb_max_burst = PJ_MAX(MAX_BURST_MSEC / ptime, max_count*3/4); - jb->jb_last_discard_seq = 0; pj_math_stat_init(&jb->jb_delay); pj_math_stat_init(&jb->jb_burst); + pjmedia_jbuf_set_discard(jb, PJMEDIA_JB_DISCARD_PROGRESSIVE); pjmedia_jbuf_reset(jb); *p_jb = jb; @@ -589,7 +612,7 @@ PJ_DEF(pj_status_t) pjmedia_jbuf_set_adaptive( pjmedia_jbuf *jb, unsigned max_prefetch) { PJ_ASSERT_RETURN(jb, PJ_EINVAL); - PJ_ASSERT_RETURN(min_prefetch < max_prefetch && + PJ_ASSERT_RETURN(min_prefetch <= max_prefetch && prefetch <= max_prefetch && max_prefetch <= jb->jb_max_count, PJ_EINVAL); @@ -602,6 +625,30 @@ PJ_DEF(pj_status_t) pjmedia_jbuf_set_adaptive( pjmedia_jbuf *jb, } +PJ_DEF(pj_status_t) pjmedia_jbuf_set_discard( pjmedia_jbuf *jb, + pjmedia_jb_discard_algo algo) +{ + PJ_ASSERT_RETURN(jb, PJ_EINVAL); + PJ_ASSERT_RETURN(algo >= PJMEDIA_JB_DISCARD_NONE && + algo <= PJMEDIA_JB_DISCARD_PROGRESSIVE, + PJ_EINVAL); + + switch(algo) { + case PJMEDIA_JB_DISCARD_PROGRESSIVE: + jb->jb_discard_algo = &jbuf_discard_progressive; + break; + case PJMEDIA_JB_DISCARD_STATIC: + jb->jb_discard_algo = &jbuf_discard_static; + break; + default: + jb->jb_discard_algo = NULL; + break; + } + + return PJ_SUCCESS; +} + + PJ_DEF(pj_status_t) pjmedia_jbuf_reset(pjmedia_jbuf *jb) { jb->jb_level = 0; @@ -610,6 +657,8 @@ PJ_DEF(pj_status_t) pjmedia_jbuf_reset(pjmedia_jbuf *jb) jb->jb_status = JB_STATUS_INITIALIZING; jb->jb_init_cycle_cnt= 0; jb->jb_max_hist_level= 0; + jb->jb_prefetching = (jb->jb_prefetch != 0); + jb->jb_discard_dist = 0; jb_framelist_reset(&jb->jb_framelist); @@ -621,11 +670,13 @@ PJ_DEF(pj_status_t) pjmedia_jbuf_destroy(pjmedia_jbuf *jb) { PJ_LOG(5, (jb->jb_name.ptr, "" "JB summary:\n" - " size=%d prefetch=%d level=%d\n" + " size=%d/eff=%d prefetch=%d level=%d\n" " delay (min/max/avg/dev)=%d/%d/%d/%d ms\n" " burst (min/max/avg/dev)=%d/%d/%d/%d frames\n" " lost=%d discard=%d empty=%d", - jb->jb_framelist.size, jb->jb_prefetch, jb->jb_eff_level, + jb_framelist_size(&jb->jb_framelist), + jb_framelist_eff_size(&jb->jb_framelist), + jb->jb_prefetch, jb->jb_eff_level, jb->jb_delay.min, jb->jb_delay.max, jb->jb_delay.mean, pj_math_stat_get_stddev(&jb->jb_delay), jb->jb_burst.min, jb->jb_burst.max, jb->jb_burst.mean, @@ -712,6 +763,136 @@ static void jbuf_calculate_jitter(pjmedia_jbuf *jb) } } + +static void jbuf_discard_static(pjmedia_jbuf *jb) +{ + /* These code is used for shortening the delay in the jitter buffer. + * It needs shrink only when there is possibility of drift. Drift + * detection is performed by inspecting the jitter buffer size, if + * its size is twice of current burst level, there can be drift. + * + * Moreover, normally drift level is quite low, so JB shouldn't need + * to shrink aggresively, it will shrink maximum one frame per + * PJMEDIA_JBUF_DISC_MIN_GAP ms. Theoritically, JB may handle drift level + * as much as = FRAME_PTIME/PJMEDIA_JBUF_DISC_MIN_GAP * 100% + * + * Whenever there is drift, where PUT > GET, this method will keep + * the latency (JB size) as much as twice of burst level. + */ + + /* Shrinking due of drift will be implicitly done by progressive discard, + * so just disable it when progressive discard is active. + */ + int diff, burst_level; + + burst_level = PJ_MAX(jb->jb_eff_level, jb->jb_level); + diff = jb_framelist_eff_size(&jb->jb_framelist) - burst_level*2; + + if (diff >= STA_DISC_SAFE_SHRINKING_DIFF) { + int seq_origin; + + /* Check and adjust jb_discard_ref, in case there was + * seq restart + */ + seq_origin = jb_framelist_origin(&jb->jb_framelist); + if (seq_origin < jb->jb_discard_ref) + jb->jb_discard_ref = seq_origin; + + if (seq_origin - jb->jb_discard_ref >= jb->jb_min_shrink_gap) + { + /* Shrink slowly, one frame per cycle */ + diff = 1; + + /* Drop frame(s)! */ + diff = jb_framelist_remove_head(&jb->jb_framelist, diff); + jb->jb_discard_ref = jb_framelist_origin(&jb->jb_framelist); + jb->jb_discard += diff; + + TRACE__((jb->jb_name.ptr, + "JB shrinking %d frame(s), cur size=%d", diff, + jb_framelist_eff_size(&jb->jb_framelist))); + } + } +} + + +static void jbuf_discard_progressive(pjmedia_jbuf *jb) +{ + unsigned cur_size, burst_level, overflow, T, discard_dist; + int last_seq; + + /* Should be done in PUT operation */ + if (jb->jb_last_op != JB_OP_PUT) + return; + + /* Check if latency is longer than burst */ + cur_size = jb_framelist_eff_size(&jb->jb_framelist); + burst_level = PJ_MAX(jb->jb_eff_level, jb->jb_level); + if (cur_size <= burst_level) { + /* Reset any scheduled discard */ + jb->jb_discard_dist = 0; + return; + } + + /* Estimate discard duration needed for adjusting latency */ + if (burst_level <= PJMEDIA_JBUF_PRO_DISC_MIN_BURST) + T = PJMEDIA_JBUF_PRO_DISC_T1; + else if (burst_level >= PJMEDIA_JBUF_PRO_DISC_MAX_BURST) + T = PJMEDIA_JBUF_PRO_DISC_T2; + else + T = PJMEDIA_JBUF_PRO_DISC_T1 + + (PJMEDIA_JBUF_PRO_DISC_T2 - PJMEDIA_JBUF_PRO_DISC_T1) * + (burst_level - PJMEDIA_JBUF_PRO_DISC_MIN_BURST) / + (PJMEDIA_JBUF_PRO_DISC_MAX_BURST-PJMEDIA_JBUF_PRO_DISC_MIN_BURST); + + /* Calculate current discard distance */ + overflow = cur_size - burst_level; + discard_dist = T / overflow / jb->jb_frame_ptime; + + /* Get last seq number in the JB */ + last_seq = jb_framelist_origin(&jb->jb_framelist) + + jb_framelist_size(&jb->jb_framelist) - 1; + + /* Setup new discard schedule if none, otherwise, update the existing + * discard schedule (can be delayed or accelerated). + */ + if (jb->jb_discard_dist == 0) { + /* Setup new discard schedule */ + jb->jb_discard_ref = last_seq; + } else if (last_seq < jb->jb_discard_ref) { + /* Seq restarted, update discard reference */ + jb->jb_discard_ref = last_seq; + } + jb->jb_discard_dist = PJ_MAX(jb->jb_min_shrink_gap, (int)discard_dist); + + /* Check if we need to discard now */ + if (last_seq >= (jb->jb_discard_ref + (int)jb->jb_discard_dist)) { + int discard_seq; + + discard_seq = jb->jb_discard_ref + jb->jb_discard_dist; + if (discard_seq < jb_framelist_origin(&jb->jb_framelist)) + discard_seq = jb_framelist_origin(&jb->jb_framelist); + + jb_framelist_discard(&jb->jb_framelist, discard_seq); + + TRACE__((jb->jb_name.ptr, + "Discard #%d: ref=#%d dist=%d orig=%d size=%d/%d " + "burst=%d/%d", + discard_seq, + jb->jb_discard_ref, + jb->jb_discard_dist, + jb_framelist_origin(&jb->jb_framelist), + cur_size, + jb_framelist_size(&jb->jb_framelist), + jb->jb_eff_level, + burst_level)); + + /* Update discard reference */ + jb->jb_discard_ref = discard_seq; + } +} + + PJ_INLINE(void) jbuf_update(pjmedia_jbuf *jb, int oper) { if(jb->jb_last_op != oper) { @@ -747,63 +928,10 @@ PJ_INLINE(void) jbuf_update(pjmedia_jbuf *jb, int oper) jb->jb_level = 0; } - /* These code is used for shortening the delay in the jitter buffer. - * It needs shrink only when there is possibility of drift. Drift - * detection is performed by inspecting the jitter buffer size, if - * its size is twice of current burst level, there can be drift. - * - * Moreover, normally drift level is quite low, so JB shouldn't need - * to shrink aggresively, it will shrink maximum one frame per - * MIN_SHRINK_GAP_MSEC ms. Theoritically, JB may handle drift level - * as much as = FRAME_PTIME/MIN_SHRINK_GAP_MSEC * 100% - * - * Whenever there is drift, where PUT > GET, this method will keep - * the latency (JB size) as much as twice of burst level. - */ - - /* Shrinking due of drift will be implicitly done by progressive discard, - * so just disable it when progressive discard is active. - */ -#if !PROGRESSIVE_DISCARD - - if (jb->jb_status != JB_STATUS_PROCESSING) - return; - - { - int diff, burst_level; - - burst_level = PJ_MAX(jb->jb_eff_level, jb->jb_level); - diff = jb_framelist_eff_size(&jb->jb_framelist) - burst_level*2; - - if (diff >= SAFE_SHRINKING_DIFF) { - int seq_origin; - - /* Check and adjust jb_last_del_seq, in case there was - * seq restart - */ - seq_origin = jb_framelist_origin(&jb->jb_framelist); - if (seq_origin < jb->jb_last_del_seq) - jb->jb_last_del_seq = seq_origin; - - if (seq_origin - jb->jb_last_del_seq >= jb->jb_min_shrink_gap) - { - /* Shrink slowly, one frame per cycle */ - diff = 1; - - /* Drop frame(s)! */ - diff = jb_framelist_remove_head(&jb->jb_framelist, diff); - jb->jb_last_del_seq = jb_framelist_origin(&jb->jb_framelist); - jb->jb_discard += diff; - - TRACE__((jb->jb_name.ptr, - "JB shrinking %d frame(s), cur size=%d", diff, - jb_framelist_eff_size(&jb->jb_framelist))); - } - } + /* Call discard algorithm */ + if (jb->jb_status == JB_STATUS_PROCESSING && jb->jb_discard_algo) { + (*jb->jb_discard_algo)(jb); } - -#endif /* !PROGRESSIVE_DISCARD */ - } PJ_DEF(void) pjmedia_jbuf_put_frame( pjmedia_jbuf *jb, @@ -834,96 +962,35 @@ PJ_DEF(void) pjmedia_jbuf_put_frame3(pjmedia_jbuf *jb, pj_bool_t *discarded) { pj_size_t min_frame_size; - int new_size, cur_size, frame_type = PJMEDIA_JB_NORMAL_FRAME; + int new_size, cur_size; pj_status_t status; cur_size = jb_framelist_eff_size(&jb->jb_framelist); -#if PROGRESSIVE_DISCARD - { - unsigned interval, seq_delta; - unsigned burst_level, burst_factor; - - /* Calculating discard interval (aggressiveness) based on - * (current size / burst level). - */ - if (jb->jb_status == JB_STATUS_PROCESSING) { - burst_level = PJ_MAX(jb->jb_eff_level, jb->jb_level); - burst_factor = cur_size / burst_level; - /* Tolerate small spikes */ - if ((burst_level <= 5) && (burst_factor < 3)) - burst_factor = 0; - } else { - burst_factor = 0; - } - - switch (burst_factor) { - case 0: - interval = 0; - break; - case 1: - interval = 7; - break; - case 2: - interval = 5; - break; - default: - interval = 4; - break; - } - - /* Do the math now to see if we should discard this packet. - * Calculate the distance from the last sequence - * discarded. If negative, then this is an out of - * order frame so just proceed with discard. Else - * see if the delta is at least the intervals worth away - * from the last frame discarded. - */ - seq_delta = (pj_uint16_t)(frame_seq - jb->jb_last_discard_seq); - if ((0 != interval) && (seq_delta >= interval)) { - frame_type = PJMEDIA_JB_DISCARDED_FRAME; - jb->jb_last_discard_seq = frame_seq; - - TRACE__((jb->jb_name.ptr, - "Discarding frame #%d: eff=%d disc=%d orig:%d" - " seq_delta:%d", - frame_seq, - cur_size, - jb_framelist_size(&jb->jb_framelist) - cur_size, - jb_framelist_origin(&jb->jb_framelist), - (int)seq_delta)); - } - } -#endif /* PROGRESSIVE_DISCARD */ - - /* Attempt to store the frame */ min_frame_size = PJ_MIN(frame_size, jb->jb_frame_size); status = jb_framelist_put_at(&jb->jb_framelist, frame_seq, frame, - min_frame_size, bit_info, ts, frame_type); + min_frame_size, bit_info, ts, + PJMEDIA_JB_NORMAL_FRAME); /* Jitter buffer is full, remove some older frames */ while (status == PJ_ETOOMANY) { int distance; unsigned removed; - /* When progressive discard activated, just remove as few as possible - * just to make this frame in. - */ -#if PROGRESSIVE_DISCARD - /* The cases of seq-jump, out-of-order, and seq restart should have + /* Remove as few as possible just to make this frame in. Note that + * the cases of seq-jump, out-of-order, and seq restart should have * been handled/normalized by previous call of jb_framelist_put_at(). * So we're confident about 'distance' value here. */ distance = (frame_seq - jb_framelist_origin(&jb->jb_framelist)) - jb->jb_max_count + 1; pj_assert(distance > 0); -#else - distance = PJ_MAX(jb->jb_max_count/4, 1); -#endif + removed = jb_framelist_remove_head(&jb->jb_framelist, distance); status = jb_framelist_put_at(&jb->jb_framelist, frame_seq, frame, - min_frame_size, bit_info, ts, frame_type); + min_frame_size, bit_info, ts, + PJMEDIA_JB_NORMAL_FRAME); jb->jb_discard += removed; } @@ -936,11 +1003,11 @@ PJ_DEF(void) pjmedia_jbuf_put_frame3(pjmedia_jbuf *jb, *discarded = (status != PJ_SUCCESS); if (status == PJ_SUCCESS) { - if (jb->jb_status == JB_STATUS_PREFETCHING) { + if (jb->jb_prefetching) { TRACE__((jb->jb_name.ptr, "PUT prefetch_cnt=%d/%d", new_size, jb->jb_prefetch)); if (new_size >= jb->jb_prefetch) - jb->jb_status = JB_STATUS_PROCESSING; + jb->jb_prefetching = PJ_FALSE; } jb->jb_level += (new_size > cur_size ? new_size-cur_size : 1); jbuf_update(jb, JB_OP_PUT); @@ -983,7 +1050,7 @@ PJ_DEF(void) pjmedia_jbuf_get_frame3(pjmedia_jbuf *jb, pj_uint32_t *ts, int *seq) { - if (jb->jb_status == JB_STATUS_PREFETCHING) { + if (jb->jb_prefetching) { /* Can't return frame because jitter buffer is filling up * minimum prefetch. @@ -1030,7 +1097,7 @@ PJ_DEF(void) pjmedia_jbuf_get_frame3(pjmedia_jbuf *jb, } else { /* Jitter buffer is empty */ if (jb->jb_prefetch) - jb->jb_status = JB_STATUS_PREFETCHING; + jb->jb_prefetching = PJ_TRUE; //pj_bzero(frame, jb->jb_frame_size); *p_frame_type = PJMEDIA_JB_ZERO_EMPTY_FRAME; diff --git a/pjmedia/src/pjmedia/sdp_neg.c b/pjmedia/src/pjmedia/sdp_neg.c index 874f1d2d..d484a91a 100644 --- a/pjmedia/src/pjmedia/sdp_neg.c +++ b/pjmedia/src/pjmedia/sdp_neg.c @@ -1330,7 +1330,10 @@ static pj_status_t match_offer(pj_pool_t *pool, if (!pj_stricmp(&or_.enc_name, &lr.enc_name) && or_.clock_rate == lr.clock_rate && (pj_stricmp(&or_.param, &lr.param)==0 || - (or_.param.slen==1 && *or_.param.ptr=='1'))) + (lr.param.slen==0 && or_.param.slen==1 && + *or_.param.ptr=='1') || + (or_.param.slen==0 && lr.param.slen==1 && + *lr.param.ptr=='1'))) { /* Match! */ if (is_codec) { diff --git a/pjmedia/src/pjmedia/session.c b/pjmedia/src/pjmedia/session.c index bc1d1754..43767790 100644 --- a/pjmedia/src/pjmedia/session.c +++ b/pjmedia/src/pjmedia/session.c @@ -272,6 +272,40 @@ PJ_DEF(pj_status_t) pjmedia_session_resume_stream( pjmedia_session *session, return pjmedia_stream_resume(session->stream[index], dir); } +/** + * Send RTCP SDES for the session. + */ +PJ_DEF(pj_status_t) +pjmedia_session_send_rtcp_sdes( const pjmedia_session *session ) +{ + unsigned i; + + PJ_ASSERT_RETURN(session, PJ_EINVAL); + + for (i=0; istream_cnt; ++i) { + pjmedia_stream_send_rtcp_sdes(session->stream[i]); + } + + return PJ_SUCCESS; +} + +/** + * Send RTCP BYE for the session. + */ +PJ_DEF(pj_status_t) +pjmedia_session_send_rtcp_bye( const pjmedia_session *session ) +{ + unsigned i; + + PJ_ASSERT_RETURN(session, PJ_EINVAL); + + for (i=0; istream_cnt; ++i) { + pjmedia_stream_send_rtcp_bye(session->stream[i]); + } + + return PJ_SUCCESS; +} + /** * Enumerate media stream in the session. */ diff --git a/pjmedia/src/pjmedia/stream.c b/pjmedia/src/pjmedia/stream.c index 8534caa8..4022c36a 100644 --- a/pjmedia/src/pjmedia/stream.c +++ b/pjmedia/src/pjmedia/stream.c @@ -154,6 +154,7 @@ struct pjmedia_stream pj_uint32_t rtcp_last_tx; /**< RTCP tx time in timestamp */ pj_uint32_t rtcp_interval; /**< Interval, in timestamp. */ pj_bool_t initial_rr; /**< Initial RTCP RR sent */ + pj_bool_t rtcp_sdes_bye_disabled;/**< Send RTCP SDES/BYE?*/ /* RFC 2833 DTMF transmission queue: */ int tx_event_pt; /**< Outgoing pt for dtmf. */ @@ -1824,19 +1825,24 @@ on_return: /* Build RR or SR */ pjmedia_rtcp_build_rtcp(&stream->rtcp, &sr_rr_pkt, &len); - pkt = (pj_uint8_t*) stream->enc->out_pkt; - pj_memcpy(pkt, sr_rr_pkt, len); - pkt += len; - - /* Append SDES */ - len = create_rtcp_sdes(stream, (pj_uint8_t*)pkt, - stream->enc->out_pkt_size - len); - if (len > 0) { - pkt += len; - len = ((pj_uint8_t*)pkt) - ((pj_uint8_t*)stream->enc->out_pkt); - pjmedia_transport_send_rtcp(stream->transport, - stream->enc->out_pkt, len); - } + + if (!stream->rtcp_sdes_bye_disabled) { + pkt = (pj_uint8_t*) stream->enc->out_pkt; + pj_memcpy(pkt, sr_rr_pkt, len); + pkt += len; + + /* Append SDES */ + len = create_rtcp_sdes(stream, (pj_uint8_t*)pkt, + stream->enc->out_pkt_size - len); + if (len > 0) { + pkt += len; + len = ((pj_uint8_t*)pkt) - ((pj_uint8_t*)stream->enc->out_pkt); + pjmedia_transport_send_rtcp(stream->transport, + stream->enc->out_pkt, len); + } + } else { + pjmedia_transport_send_rtcp(stream->transport, sr_rr_pkt, len); + } stream->initial_rr = PJ_TRUE; } @@ -1959,7 +1965,7 @@ PJ_DEF(pj_status_t) pjmedia_stream_create( pjmedia_endpt *endpt, enum { M = 32 }; pjmedia_stream *stream; pj_str_t name; - unsigned jb_init, jb_max, jb_min_pre, jb_max_pre, len; + unsigned jb_init, jb_max, jb_min_pre, jb_max_pre; pjmedia_audio_format_detail *afd; pj_pool_t *own_pool = NULL; char *p; @@ -2010,6 +2016,7 @@ PJ_DEF(pj_status_t) pjmedia_stream_create( pjmedia_endpt *endpt, stream->user_data = user_data; stream->rtcp_interval = (PJMEDIA_RTCP_INTERVAL-500 + (pj_rand()%1000)) * info->fmt.clock_rate / 1000; + stream->rtcp_sdes_bye_disabled = info->rtcp_sdes_bye_disabled; stream->tx_event_pt = info->tx_event_pt ? info->tx_event_pt : -1; stream->rx_event_pt = info->rx_event_pt ? info->rx_event_pt : -1; @@ -2322,11 +2329,8 @@ PJ_DEF(pj_status_t) pjmedia_stream_create( pjmedia_endpt *endpt, #endif /* Send RTCP SDES */ - len = create_rtcp_sdes(stream, (pj_uint8_t*)stream->enc->out_pkt, - stream->enc->out_pkt_size); - if (len != 0) { - pjmedia_transport_send_rtcp(stream->transport, - stream->enc->out_pkt, len); + if (!stream->rtcp_sdes_bye_disabled) { + pjmedia_stream_send_rtcp_sdes(stream); } #if defined(PJMEDIA_STREAM_ENABLE_KA) && PJMEDIA_STREAM_ENABLE_KA!=0 @@ -2343,7 +2347,7 @@ PJ_DEF(pj_status_t) pjmedia_stream_create( pjmedia_endpt *endpt, pj_ansi_snprintf(trace_name, sizeof(trace_name), TRACE_JB_PATH_PREFIX "%s.csv", stream->port.info.name.ptr); - status = pj_file_open(pool, trace_name, PJ_O_RDWR, &stream->trace_jb_fd); + status = pj_file_open(pool, trace_name, PJ_O_WRONLY, &stream->trace_jb_fd); if (status != PJ_SUCCESS) { stream->trace_jb_fd = TRACE_JB_INVALID_FD; PJ_LOG(3,(THIS_FILE, "Failed creating RTP trace file '%s'", @@ -2381,7 +2385,6 @@ err_cleanup: */ PJ_DEF(pj_status_t) pjmedia_stream_destroy( pjmedia_stream *stream ) { - unsigned len; PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); #if defined(PJMEDIA_HAS_RTCP_XR) && (PJMEDIA_HAS_RTCP_XR != 0) @@ -2423,13 +2426,8 @@ PJ_DEF(pj_status_t) pjmedia_stream_destroy( pjmedia_stream *stream ) #endif /* Send RTCP BYE */ - if (stream->enc && stream->transport) { - len = create_rtcp_bye(stream, (pj_uint8_t*)stream->enc->out_pkt, - stream->enc->out_pkt_size); - if (len != 0) { - pjmedia_transport_send_rtcp(stream->transport, - stream->enc->out_pkt, len); - } + if (!stream->rtcp_sdes_bye_disabled) { + pjmedia_stream_send_rtcp_bye(stream); } /* Detach from transport @@ -3298,3 +3296,45 @@ PJ_DEF(pj_status_t) pjmedia_stream_info_from_sdp( return status; } + +/* + * Send RTCP SDES. + */ +PJ_DEF(pj_status_t) +pjmedia_stream_send_rtcp_sdes( pjmedia_stream *stream ) +{ + unsigned len; + + PJ_ASSERT_RETURN(stream, PJ_EINVAL); + + len = create_rtcp_sdes(stream, (pj_uint8_t*)stream->enc->out_pkt, + stream->enc->out_pkt_size); + if (len != 0) { + return pjmedia_transport_send_rtcp(stream->transport, + stream->enc->out_pkt, len); + } + + return PJ_SUCCESS; +} + +/* + * Send RTCP BYE. + */ +PJ_DEF(pj_status_t) +pjmedia_stream_send_rtcp_bye( pjmedia_stream *stream ) +{ + PJ_ASSERT_RETURN(stream, PJ_EINVAL); + + if (stream->enc && stream->transport) { + unsigned len; + + len = create_rtcp_bye(stream, (pj_uint8_t*)stream->enc->out_pkt, + stream->enc->out_pkt_size); + if (len != 0) { + return pjmedia_transport_send_rtcp(stream->transport, + stream->enc->out_pkt, len); + } + } + + return PJ_SUCCESS; +} diff --git a/pjmedia/src/pjmedia/transport_adapter_sample.c b/pjmedia/src/pjmedia/transport_adapter_sample.c index c118c108..a076bdab 100644 --- a/pjmedia/src/pjmedia/transport_adapter_sample.c +++ b/pjmedia/src/pjmedia/transport_adapter_sample.c @@ -94,6 +94,7 @@ static struct pjmedia_transport_op tp_adapter_op = struct tp_adapter { pjmedia_transport base; + pj_bool_t del_base; pj_pool_t *pool; @@ -118,6 +119,7 @@ struct tp_adapter PJ_DEF(pj_status_t) pjmedia_tp_adapter_create( pjmedia_endpt *endpt, const char *name, pjmedia_transport *transport, + pj_bool_t del_base, pjmedia_transport **p_tp) { pj_pool_t *pool; @@ -138,6 +140,7 @@ PJ_DEF(pj_status_t) pjmedia_tp_adapter_create( pjmedia_endpt *endpt, /* Save the transport as the slave transport */ adapter->slave_tp = transport; + adapter->del_base = del_base; /* Done */ *p_tp = &adapter->base; @@ -421,7 +424,9 @@ static pj_status_t transport_destroy (pjmedia_transport *tp) struct tp_adapter *adapter = (struct tp_adapter*)tp; /* Close the slave transport */ - pjmedia_transport_close(adapter->slave_tp); + if (adapter->del_base) { + pjmedia_transport_close(adapter->slave_tp); + } /* Self destruct.. */ pj_pool_release(adapter->pool); diff --git a/pjmedia/src/pjmedia/transport_ice.c b/pjmedia/src/pjmedia/transport_ice.c index 54ae0324..38bd70d4 100644 --- a/pjmedia/src/pjmedia/transport_ice.c +++ b/pjmedia/src/pjmedia/transport_ice.c @@ -72,6 +72,7 @@ struct transport_ice pj_sockaddr rtp_src_addr; /**< Actual source RTP address. */ pj_sockaddr rtcp_src_addr; /**< Actual source RTCP address. */ unsigned rtp_src_cnt; /**< How many pkt from this addr. */ + unsigned rtcp_src_cnt; /**< How many pkt from this addr. */ unsigned tx_drop_pct; /**< Percent of tx pkts to drop. */ unsigned rx_drop_pct; /**< Percent of rx pkts to drop. */ @@ -1516,6 +1517,8 @@ static pj_status_t transport_get_info(pjmedia_transport *tp, */ if (tp_ice->use_ice || tp_ice->rtp_src_cnt) { info->src_rtp_name = tp_ice->rtp_src_addr; + } + if (tp_ice->use_ice || tp_ice->rtcp_src_cnt) { info->src_rtcp_name = tp_ice->rtcp_src_addr; } @@ -1581,6 +1584,7 @@ static pj_status_t transport_attach (pjmedia_transport *tp, tp_ice->rtp_src_addr = tp_ice->remote_rtp; tp_ice->rtcp_src_addr = tp_ice->remote_rtcp; tp_ice->rtp_src_cnt = 0; + tp_ice->rtcp_src_cnt = 0; return PJ_SUCCESS; } @@ -1656,6 +1660,7 @@ static void ice_on_rx_data(pj_ice_strans *ice_st, unsigned comp_id, unsigned src_addr_len) { struct transport_ice *tp_ice; + pj_bool_t discard = PJ_FALSE; tp_ice = (struct transport_ice*) pj_ice_strans_get_user_data(ice_st); @@ -1671,20 +1676,23 @@ static void ice_on_rx_data(pj_ice_strans *ice_st, unsigned comp_id, } } - (*tp_ice->rtp_cb)(tp_ice->stream, pkt, size); - /* See if source address of RTP packet is different than the * configured address, and switch RTP remote address to * source packet address after several consecutive packets * have been received. */ if (!tp_ice->use_ice) { + pj_bool_t enable_switch = + ((tp_ice->options & PJMEDIA_ICE_NO_SRC_ADDR_CHECKING)==0); - /* Increment counter and avoid zero */ - if (++tp_ice->rtp_src_cnt == 0) - tp_ice->rtp_src_cnt = 1; + if (!enable_switch || + pj_sockaddr_cmp(&tp_ice->remote_rtp, src_addr) == 0) + { + /* Don't switch while we're receiving from remote_rtp */ + tp_ice->rtp_src_cnt = 0; + } else { - if (pj_sockaddr_cmp(&tp_ice->remote_rtp, src_addr) != 0) { + ++tp_ice->rtp_src_cnt; /* Check if the source address is recognized. */ if (pj_sockaddr_cmp(src_addr, &tp_ice->rtp_src_addr) != 0) { @@ -1692,11 +1700,12 @@ static void ice_on_rx_data(pj_ice_strans *ice_st, unsigned comp_id, pj_sockaddr_cp(&tp_ice->rtp_src_addr, src_addr); /* Reset counter */ tp_ice->rtp_src_cnt = 0; + discard = PJ_TRUE; } - if ((tp_ice->options & PJMEDIA_ICE_NO_SRC_ADDR_CHECKING)==0 && - tp_ice->rtp_src_cnt >= PJMEDIA_RTP_NAT_PROBATION_CNT) - { + if (tp_ice->rtp_src_cnt < PJMEDIA_RTP_NAT_PROBATION_CNT) { + discard = PJ_TRUE; + } else { char addr_text[80]; /* Set remote RTP address to source address */ @@ -1725,7 +1734,8 @@ static void ice_on_rx_data(pj_ice_strans *ice_st, unsigned comp_id, pj_sockaddr_set_port(&tp_ice->remote_rtcp, port); PJ_LOG(4,(tp_ice->base.name, - "Remote RTCP address switched to %s", + "Remote RTCP address switched to predicted " + "address %s", pj_sockaddr_print(&tp_ice->remote_rtcp, addr_text, sizeof(addr_text), 3))); @@ -1733,31 +1743,45 @@ static void ice_on_rx_data(pj_ice_strans *ice_st, unsigned comp_id, } } } + + if (!discard) + (*tp_ice->rtp_cb)(tp_ice->stream, pkt, size); + } else if (comp_id==2 && tp_ice->rtcp_cb) { - (*tp_ice->rtcp_cb)(tp_ice->stream, pkt, size); /* Check if RTCP source address is the same as the configured * remote address, and switch the address when they are * different. */ if (!tp_ice->use_ice && - pj_sockaddr_cmp(&tp_ice->remote_rtcp, src_addr) != 0) + (tp_ice->options & PJMEDIA_ICE_NO_SRC_ADDR_CHECKING)==0) { - pj_sockaddr_cp(&tp_ice->rtcp_src_addr, src_addr); - - if ((tp_ice->options & PJMEDIA_ICE_NO_SRC_ADDR_CHECKING)==0) { + if (pj_sockaddr_cmp(&tp_ice->remote_rtcp, src_addr) == 0) { + tp_ice->rtcp_src_cnt = 0; + } else { char addr_text[80]; - pj_sockaddr_cp(&tp_ice->remote_rtcp, src_addr); + ++tp_ice->rtcp_src_cnt; + if (tp_ice->rtcp_src_cnt < PJMEDIA_RTCP_NAT_PROBATION_CNT) { + discard = PJ_TRUE; + } else { + tp_ice->rtcp_src_cnt = 0; + pj_sockaddr_cp(&tp_ice->rtcp_src_addr, src_addr); + pj_sockaddr_cp(&tp_ice->remote_rtcp, src_addr); - pj_assert(tp_ice->addr_len == pj_sockaddr_get_len(src_addr)); + pj_assert(tp_ice->addr_len==pj_sockaddr_get_len(src_addr)); - PJ_LOG(4,(tp_ice->base.name, - "Remote RTCP address switched to %s", - pj_sockaddr_print(&tp_ice->remote_rtcp, addr_text, - sizeof(addr_text), 3))); + PJ_LOG(4,(tp_ice->base.name, + "Remote RTCP address switched to %s", + pj_sockaddr_print(&tp_ice->remote_rtcp, + addr_text, sizeof(addr_text), + 3))); + } } } + + if (!discard) + (*tp_ice->rtcp_cb)(tp_ice->stream, pkt, size); } PJ_UNUSED_ARG(src_addr_len); diff --git a/pjmedia/src/pjmedia/transport_udp.c b/pjmedia/src/pjmedia/transport_udp.c index 59d2dc70..63178599 100644 --- a/pjmedia/src/pjmedia/transport_udp.c +++ b/pjmedia/src/pjmedia/transport_udp.c @@ -83,6 +83,7 @@ struct transport_udp pj_sock_t rtcp_sock; /**< RTCP socket */ pj_sockaddr rtcp_addr_name; /**< Published RTCP address. */ pj_sockaddr rtcp_src_addr; /**< Actual source RTCP address. */ + unsigned rtcp_src_cnt; /**< How many pkt from this addr. */ int rtcp_addr_len; /**< Length of RTCP src address. */ pj_ioqueue_key_t *rtcp_key; /**< RTCP socket key in ioqueue */ pj_ioqueue_op_key_t rtcp_read_op; /**< Pending read operation */ @@ -452,6 +453,7 @@ static void on_rx_rtp( pj_ioqueue_key_t *key, do { void (*cb)(void*,void*,pj_ssize_t); void *user_data; + pj_bool_t discard = PJ_FALSE; cb = udp->rtp_cb; user_data = udp->user_data; @@ -462,14 +464,10 @@ static void on_rx_rtp( pj_ioqueue_key_t *key, PJ_LOG(5,(udp->base.name, "RX RTP packet dropped because of pkt lost " "simulation")); - goto read_next_packet; + discard = PJ_TRUE; } } - - if (udp->attached && cb) - (*cb)(user_data, udp->rtp_pkt, bytes_read); - /* See if source address of RTP packet is different than the * configured address, and switch RTP remote address to * source packet address after several consecutive packets @@ -478,11 +476,15 @@ static void on_rx_rtp( pj_ioqueue_key_t *key, if (bytes_read>0 && (udp->options & PJMEDIA_UDP_NO_SRC_ADDR_CHECKING)==0) { - if (pj_sockaddr_cmp(&udp->rem_rtp_addr, &udp->rtp_src_addr) != 0) { - + if (pj_sockaddr_cmp(&udp->rem_rtp_addr, &udp->rtp_src_addr) == 0) { + /* We're still receiving from rem_rtp_addr. Don't switch. */ + udp->rtp_src_cnt = 0; + } else { udp->rtp_src_cnt++; - if (udp->rtp_src_cnt >= PJMEDIA_RTP_NAT_PROBATION_CNT) { + if (udp->rtp_src_cnt < PJMEDIA_RTP_NAT_PROBATION_CNT) { + discard = PJ_TRUE; + } else { char addr_text[80]; @@ -516,7 +518,8 @@ static void on_rx_rtp( pj_ioqueue_key_t *key, sizeof(pj_sockaddr)); PJ_LOG(4,(udp->base.name, - "Remote RTCP address switched to %s", + "Remote RTCP address switched to predicted" + " address %s", pj_sockaddr_print(&udp->rtcp_src_addr, addr_text, sizeof(addr_text), 3))); @@ -526,7 +529,9 @@ static void on_rx_rtp( pj_ioqueue_key_t *key, } } -read_next_packet: + if (!discard && udp->attached && cb) + (*cb)(user_data, udp->rtp_pkt, bytes_read); + bytes_read = sizeof(udp->rtp_pkt); udp->rtp_addrlen = sizeof(udp->rtp_src_addr); status = pj_ioqueue_recvfrom(udp->rtp_key, &udp->rtp_read_op, @@ -568,18 +573,27 @@ static void on_rx_rtcp(pj_ioqueue_key_t *key, * different. */ if (bytes_read>0 && - (udp->options & PJMEDIA_UDP_NO_SRC_ADDR_CHECKING)==0 && - pj_sockaddr_cmp(&udp->rem_rtcp_addr, &udp->rtcp_src_addr) != 0) + (udp->options & PJMEDIA_UDP_NO_SRC_ADDR_CHECKING)==0) { - char addr_text[80]; + if (pj_sockaddr_cmp(&udp->rem_rtcp_addr, &udp->rtcp_src_addr) == 0) { + /* Still receiving from rem_rtcp_addr, don't switch */ + udp->rtcp_src_cnt = 0; + } else { + ++udp->rtcp_src_cnt; - pj_memcpy(&udp->rem_rtcp_addr, &udp->rtcp_src_addr, - sizeof(pj_sockaddr)); + if (udp->rtcp_src_cnt >= PJMEDIA_RTCP_NAT_PROBATION_CNT ) { + char addr_text[80]; + + udp->rtcp_src_cnt = 0; + pj_memcpy(&udp->rem_rtcp_addr, &udp->rtcp_src_addr, + sizeof(pj_sockaddr)); - PJ_LOG(4,(udp->base.name, - "Remote RTCP address switched to %s", - pj_sockaddr_print(&udp->rtcp_src_addr, addr_text, - sizeof(addr_text), 3))); + PJ_LOG(4,(udp->base.name, + "Remote RTCP address switched to %s", + pj_sockaddr_print(&udp->rtcp_src_addr, addr_text, + sizeof(addr_text), 3))); + } + } } bytes_read = sizeof(udp->rtcp_pkt); @@ -677,6 +691,7 @@ static pj_status_t transport_attach( pjmedia_transport *tp, pj_bzero(&udp->rtp_src_addr, sizeof(udp->rtp_src_addr)); pj_bzero(&udp->rtcp_src_addr, sizeof(udp->rtcp_src_addr)); udp->rtp_src_cnt = 0; + udp->rtcp_src_cnt = 0; /* Unlock keys */ pj_ioqueue_unlock_key(udp->rtcp_key); diff --git a/pjmedia/src/test/jbuf_test.c b/pjmedia/src/test/jbuf_test.c index 9cffa921..309459b0 100644 --- a/pjmedia/src/test/jbuf_test.c +++ b/pjmedia/src/test/jbuf_test.c @@ -43,7 +43,8 @@ typedef struct test_cond_t { int discard; int lost; int empty; - int delay; /**< Maximum delay, in frames. */ + int delay; /**< Average delay, in frames. */ + int delay_min; /**< Minimum delay, in frames. */ } test_cond_t; static pj_bool_t parse_test_headers(char *line, test_param_t *param, @@ -69,6 +70,8 @@ static pj_bool_t parse_test_headers(char *line, test_param_t *param, cond->burst = cond_val; else if (pj_ansi_stricmp(cond_st, "delay") == 0) cond->delay = cond_val; + else if (pj_ansi_stricmp(cond_st, "delay_min") == 0) + cond->delay_min = cond_val; else if (pj_ansi_stricmp(cond_st, "discard") == 0) cond->discard = cond_val; else if (pj_ansi_stricmp(cond_st, "empty") == 0) @@ -217,6 +220,7 @@ int jbuf_main(void) cond.burst = -1; cond.delay = -1; + cond.delay_min = -1; cond.discard = -1; cond.empty = -1; cond.lost = -1; @@ -313,6 +317,11 @@ int jbuf_main(void) cond.delay, state.avg_delay/JB_PTIME); rc |= 2; } + if (cond.delay_min >= 0 && (int)state.min_delay/JB_PTIME > cond.delay_min) { + printf("! 'Minimum delay' should be %d, it is %d\n", + cond.delay_min, state.min_delay/JB_PTIME); + rc |= 32; + } if (cond.discard >= 0 && (int)state.discard > cond.discard) { printf("! 'Discard' should be %d, it is %d\n", cond.discard, state.discard); diff --git a/pjmedia/src/test/mips_test.c b/pjmedia/src/test/mips_test.c index 91551196..977fe9a0 100644 --- a/pjmedia/src/test/mips_test.c +++ b/pjmedia/src/test/mips_test.c @@ -916,6 +916,23 @@ static pjmedia_port* g7221c_encode_decode(pj_pool_t *pool, } #endif /* PJMEDIA_HAS_G7221_CODEC */ +#if PJMEDIA_HAS_OPENCORE_AMRNB_CODEC +/* AMR-NB benchmark benchmark */ +static pjmedia_port* amr_encode_decode(pj_pool_t *pool, + unsigned clock_rate, + unsigned channel_count, + unsigned samples_per_frame, + unsigned flags, + struct test_entry *te) +{ + return codec_encode_decode(pool, "AMR/8000", + &pjmedia_codec_opencore_amrnb_init, + &pjmedia_codec_opencore_amrnb_deinit, + clock_rate, channel_count, + samples_per_frame, flags, te); +} +#endif /* PJMEDIA_HAS_OPENCORE_AMRNB_CODEC */ + #if defined(PJMEDIA_HAS_L16_CODEC) && PJMEDIA_HAS_L16_CODEC!=0 static pj_status_t init_l16_default(pjmedia_endpt *endpt) { @@ -1976,6 +1993,23 @@ static pjmedia_port* create_stream_g7221c( pj_pool_t *pool, } #endif /* PJMEDIA_HAS_G7221_CODEC */ +/* AMR-NB stream */ +#if PJMEDIA_HAS_OPENCORE_AMRNB_CODEC +static pjmedia_port* create_stream_amr( pj_pool_t *pool, + unsigned clock_rate, + unsigned channel_count, + unsigned samples_per_frame, + unsigned flags, + struct test_entry *te) +{ + return create_stream(pool, "AMR/8000", &pjmedia_codec_opencore_amrnb_init, + &pjmedia_codec_opencore_amrnb_deinit, + PJ_FALSE, PJ_FALSE, PJ_FALSE, + clock_rate, channel_count, + samples_per_frame, flags, te); +} +#endif /* PJMEDIA_HAS_OPENCORE_AMRNB_CODEC */ + /***************************************************************************/ /* Delay buffer */ enum {DELAY_BUF_MAX_DELAY = 80}; @@ -2366,6 +2400,9 @@ int mips_test(void) { "codec encode/decode - G.722.1", OP_PUT, K16, &g7221_encode_decode}, { "codec encode/decode - G.722.1c", OP_PUT, K32, &g7221c_encode_decode}, #endif +#if PJMEDIA_HAS_OPENCORE_AMRNB_CODEC + { "codec encode/decode - AMR-NB", OP_PUT, K8, &amr_encode_decode}, +#endif #if PJMEDIA_HAS_L16_CODEC { "codec encode/decode - L16/8000/1", OP_PUT, K8, &l16_8_encode_decode}, { "codec encode/decode - L16/16000/1", OP_PUT, K16, &l16_16_encode_decode}, @@ -2390,6 +2427,9 @@ int mips_test(void) #if PJMEDIA_HAS_G7221_CODEC { "stream TX/RX - G.722.1", OP_PUT_GET, K16, &create_stream_g7221}, { "stream TX/RX - G.722.1c", OP_PUT_GET, K32, &create_stream_g7221c}, +#endif +#if PJMEDIA_HAS_OPENCORE_AMRNB_CODEC + { "stream TX/RX - AMR-NB", OP_PUT_GET, K8, &create_stream_amr}, #endif }; diff --git a/pjmedia/src/test/test.c b/pjmedia/src/test/test.c index 82b20e17..2b7c9ca5 100644 --- a/pjmedia/src/test/test.c +++ b/pjmedia/src/test/test.c @@ -43,6 +43,17 @@ void app_perror(pj_status_t status, const char *msg) PJ_LOG(3,(THIS_FILE, "%s: %s", msg, errbuf)); } +/* Force linking PLC stuff if G.711 is disabled. See: + * https://trac.pjsip.org/repos/ticket/1337 + */ +#if PJMEDIA_HAS_G711_CODEC==0 +int dummy() +{ + // Dummy + return (int) &pjmedia_plc_save; +} +#endif + int test_main(void) { int rc = 0; -- cgit v1.2.3