diff options
44 files changed, 1553 insertions, 578 deletions
@@ -17,6 +17,13 @@ app_fax * The app_fax module is now deprecated, users should migrate to the replacement module res_fax. +app_originate +------------------ + * An 'a' option has been added to the Originate dialplan application which + will execute the originate in an asynchronous fashion. If set then the + application will return immediately without waiting for the originated + channel to answer. + Build System ------------------ * MALLOC_DEBUG no longer has an effect on Asterisk's ABI. Asterisk built @@ -115,6 +122,11 @@ res_pjproject MALLOC_DEBUG. The cache gets in the way of determining if the pool contents are used after free and who freed it. +res_pjsip_notify +------------------ + * Extend the PJSIPNotify AMI command to send an in-dialog notify on a + channel. + ------------------------------------------------------------------------------ --- Functionality changes from Asterisk 15.2.0 to Asterisk 15.3.0 ------------ ------------------------------------------------------------------------------ diff --git a/addons/cdr_mysql.c b/addons/cdr_mysql.c index 2fefe4ed1..97ebdf26f 100644 --- a/addons/cdr_mysql.c +++ b/addons/cdr_mysql.c @@ -58,6 +58,14 @@ #define DATE_FORMAT "%Y-%m-%d %T" +#ifndef MYSQL_PORT +# ifdef MARIADB_PORT +# define MYSQL_PORT MARIADB_PORT +# else +# define MYSQL_PORT 3306 +# endif +#endif + AST_THREADSTORAGE(sql1_buf); AST_THREADSTORAGE(sql2_buf); AST_THREADSTORAGE(escape_buf); diff --git a/apps/app_confbridge.c b/apps/app_confbridge.c index 4f1108e6b..25cf2758f 100644 --- a/apps/app_confbridge.c +++ b/apps/app_confbridge.c @@ -1543,6 +1543,14 @@ static struct confbridge_conference *join_conference_bridge(const char *conferen } else if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_VIDEO_SRC_SFU)) { ast_bridge_set_sfu_video_mode(conference->bridge); ast_bridge_set_video_update_discard(conference->bridge, conference->b_profile.video_update_discard); + ast_bridge_set_remb_send_interval(conference->bridge, conference->b_profile.remb_send_interval); + if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_REMB_BEHAVIOR_AVERAGE)) { + ast_brige_set_remb_behavior(conference->bridge, AST_BRIDGE_VIDEO_SFU_REMB_AVERAGE); + } else if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_REMB_BEHAVIOR_LOWEST)) { + ast_brige_set_remb_behavior(conference->bridge, AST_BRIDGE_VIDEO_SFU_REMB_LOWEST); + } else if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_REMB_BEHAVIOR_HIGHEST)) { + ast_brige_set_remb_behavior(conference->bridge, AST_BRIDGE_VIDEO_SFU_REMB_HIGHEST); + } } /* Link it into the conference bridges container */ diff --git a/apps/app_originate.c b/apps/app_originate.c index 30fa565be..107be846d 100644 --- a/apps/app_originate.c +++ b/apps/app_originate.c @@ -74,6 +74,10 @@ static const char app_originate[] = "Originate"; </parameter> <parameter name="options" required="false"> <optionlist> + <option name="a"> + <para>Originate asynchronously. In other words, continue in the dialplan + without waiting for the originated channel to answer.</para> + </option> <option name="b" argsep="^"> <para>Before originating the outgoing call, Gosub to the specified location using the newly created channel.</para> @@ -123,6 +127,7 @@ static const char app_originate[] = "Originate"; enum { OPT_PREDIAL_CALLEE = (1 << 0), OPT_PREDIAL_CALLER = (1 << 1), + OPT_ASYNC = (1 << 2), }; enum { @@ -133,6 +138,7 @@ enum { }; AST_APP_OPTIONS(originate_exec_options, BEGIN_OPTIONS + AST_APP_OPTION('a', OPT_ASYNC), AST_APP_OPTION_ARG('b', OPT_PREDIAL_CALLEE, OPT_ARG_PREDIAL_CALLEE), AST_APP_OPTION_ARG('B', OPT_PREDIAL_CALLER, OPT_ARG_PREDIAL_CALLER), END_OPTIONS ); @@ -250,7 +256,8 @@ static int originate_exec(struct ast_channel *chan, const char *data) res = ast_pbx_outgoing_exten_predial(chantech, cap_slin, chandata, timeout * 1000, args.arg1, exten, priority, &outgoing_status, - AST_OUTGOING_WAIT, NULL, NULL, NULL, NULL, NULL, 0, NULL, + ast_test_flag64(&opts, OPT_ASYNC) ? AST_OUTGOING_NO_WAIT : AST_OUTGOING_WAIT, + NULL, NULL, NULL, NULL, NULL, 0, NULL, predial_callee); } else { ast_debug(1, "Originating call to '%s/%s' and connecting them to %s(%s)\n", @@ -258,7 +265,8 @@ static int originate_exec(struct ast_channel *chan, const char *data) res = ast_pbx_outgoing_app_predial(chantech, cap_slin, chandata, timeout * 1000, args.arg1, args.arg2, &outgoing_status, - AST_OUTGOING_WAIT, NULL, NULL, NULL, NULL, NULL, NULL, + ast_test_flag64(&opts, OPT_ASYNC) ? AST_OUTGOING_NO_WAIT : AST_OUTGOING_WAIT, + NULL, NULL, NULL, NULL, NULL, NULL, predial_callee); } diff --git a/apps/confbridge/conf_config_parser.c b/apps/confbridge/conf_config_parser.c index 71da80206..c143e39e2 100644 --- a/apps/confbridge/conf_config_parser.c +++ b/apps/confbridge/conf_config_parser.c @@ -458,6 +458,39 @@ video update requests from clients. </para></description> </configOption> + <configOption name="remb_send_interval" default="0"> + <synopsis>Sets the interval in milliseconds that a combined REMB frame will be sent to video sources</synopsis> + <description><para> + Sets the interval in milliseconds that a combined REMB frame will be sent + to video sources. This is done by taking all REMB frames that have been + received since the last REMB frame was sent, making a combined value, + and sending it to the source. A REMB frame contains receiver estimated + maximum bitrate information. By creating a combined REMB frame the + sender of video can be influenced on the bitrate they choose, allowing + better quality for all receivers. + </para></description> + </configOption> + <configOption name="remb_behavior" default="average"> + <synopsis>Sets how REMB reports are generated from multiple sources</synopsis> + <description><para> + Sets how REMB reports are combined from multiple sources to form one. A REMB report + consists of information about the receiver estimated maximum bitrate. As a source + stream may be forwarded to multiple receivers the reports must be combined into + a single one which is sent to the sender.</para> + <enumlist> + <enum name="average"> + <para>The average of all estimated maximum bitrates is taken and sent + to the sender.</para> + </enum> + <enum name="lowest"> + <para>The lowest estimated maximum bitrate is forwarded to the sender.</para> + </enum> + <enum name="highest"> + <para>The highest estimated maximum bitrate is forwarded to the sender.</para> + </enum> + </enumlist> + </description> + </configOption> <configOption name="template"> <synopsis>When using the CONFBRIDGE dialplan function, use a bridge profile as a template for creating a new temporary profile</synopsis> </configOption> @@ -1661,6 +1694,24 @@ static char *handle_cli_confbridge_show_bridge_profile(struct ast_cli_entry *e, } ast_cli(a->fd,"Video Update Discard: %u\n", b_profile.video_update_discard); + ast_cli(a->fd,"REMB Send Interval: %u\n", b_profile.remb_send_interval); + + switch (b_profile.flags + & (BRIDGE_OPT_REMB_BEHAVIOR_AVERAGE | BRIDGE_OPT_REMB_BEHAVIOR_LOWEST + | BRIDGE_OPT_REMB_BEHAVIOR_HIGHEST)) { + case BRIDGE_OPT_REMB_BEHAVIOR_AVERAGE: + ast_cli(a->fd, "REMB Behavior: average\n"); + break; + case BRIDGE_OPT_REMB_BEHAVIOR_LOWEST: + ast_cli(a->fd, "REMB Behavior: lowest\n"); + break; + case BRIDGE_OPT_REMB_BEHAVIOR_HIGHEST: + ast_cli(a->fd, "REMB Behavior: highest\n"); + break; + default: + ast_assert(0); + break; + } ast_cli(a->fd,"sound_only_person: %s\n", conf_get_sound(CONF_SOUND_ONLY_PERSON, b_profile.sounds)); ast_cli(a->fd,"sound_only_one: %s\n", conf_get_sound(CONF_SOUND_ONLY_ONE, b_profile.sounds)); @@ -1998,6 +2049,30 @@ static int video_mode_handler(const struct aco_option *opt, struct ast_variable return 0; } +static int remb_behavior_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) +{ + struct bridge_profile *b_profile = obj; + + if (strcasecmp(var->name, "remb_behavior")) { + return -1; + } + + ast_clear_flag(b_profile, BRIDGE_OPT_REMB_BEHAVIOR_AVERAGE | + BRIDGE_OPT_REMB_BEHAVIOR_LOWEST | + BRIDGE_OPT_REMB_BEHAVIOR_HIGHEST); + + if (!strcasecmp(var->value, "average")) { + ast_set_flag(b_profile, BRIDGE_OPT_REMB_BEHAVIOR_AVERAGE); + } else if (!strcasecmp(var->value, "lowest")) { + ast_set_flag(b_profile, BRIDGE_OPT_REMB_BEHAVIOR_LOWEST); + } else if (!strcasecmp(var->value, "highest")) { + ast_set_flag(b_profile, BRIDGE_OPT_REMB_BEHAVIOR_HIGHEST); + } else { + return -1; + } + return 0; +} + static int user_template_handler(const struct aco_option *opt, struct ast_variable *var, void *obj) { struct user_profile *u_profile = obj; @@ -2231,6 +2306,8 @@ int conf_load_config(void) aco_option_register(&cfg_info, "language", ACO_EXACT, bridge_types, "en", OPT_CHAR_ARRAY_T, 0, CHARFLDSET(struct bridge_profile, language)); aco_option_register_custom(&cfg_info, "sound_", ACO_PREFIX, bridge_types, NULL, sound_option_handler, 0); aco_option_register(&cfg_info, "video_update_discard", ACO_EXACT, bridge_types, "2000", OPT_UINT_T, 0, FLDSET(struct bridge_profile, video_update_discard)); + aco_option_register(&cfg_info, "remb_send_interval", ACO_EXACT, bridge_types, "0", OPT_UINT_T, 0, FLDSET(struct bridge_profile, remb_send_interval)); + aco_option_register_custom(&cfg_info, "remb_behavior", ACO_EXACT, bridge_types, "average", remb_behavior_handler, 0); /* This option should only be used with the CONFBRIDGE dialplan function */ aco_option_register_custom(&cfg_info, "template", ACO_EXACT, bridge_types, NULL, bridge_template_handler, 0); diff --git a/apps/confbridge/include/confbridge.h b/apps/confbridge/include/confbridge.h index 044ab4003..0a0a5713f 100644 --- a/apps/confbridge/include/confbridge.h +++ b/apps/confbridge/include/confbridge.h @@ -76,6 +76,9 @@ enum bridge_profile_flags { BRIDGE_OPT_RECORD_FILE_TIMESTAMP = (1 << 5), /*!< Set if the record file should have a timestamp appended */ BRIDGE_OPT_BINAURAL_ACTIVE = (1 << 6), /*!< Set if binaural convolution is activated */ BRIDGE_OPT_VIDEO_SRC_SFU = (1 << 7), /*!< Selective forwarding unit */ + BRIDGE_OPT_REMB_BEHAVIOR_AVERAGE = (1 << 8), /*!< The average of all REMB reports is sent to the sender */ + BRIDGE_OPT_REMB_BEHAVIOR_LOWEST = (1 << 9), /*!< The lowest estimated maximum bitrate is sent to the sender */ + BRIDGE_OPT_REMB_BEHAVIOR_HIGHEST = (1 << 10), /*!< The highest estimated maximum bitrate is sent to the sender */ }; enum conf_menu_action_id { @@ -222,6 +225,7 @@ struct bridge_profile { struct bridge_profile_sounds *sounds; char regcontext[AST_MAX_CONTEXT]; unsigned int video_update_discard; /*!< Amount of time after sending a video update request that subsequent requests should be discarded */ + unsigned int remb_send_interval; /*!< Interval at which a combined REMB frame is sent to video sources */ }; /*! \brief The structure that represents a conference bridge */ diff --git a/autoconf/ast_ext_tool_check.m4 b/autoconf/ast_ext_tool_check.m4 index ef762eb87..cbe109e4a 100644 --- a/autoconf/ast_ext_tool_check.m4 +++ b/autoconf/ast_ext_tool_check.m4 @@ -11,7 +11,7 @@ AC_DEFUN([AST_EXT_TOOL_CHECK], AC_PATH_TOOL(CONFIG_$1, $2, No, [${$1_DIR}/bin:$PATH]) if test ! "x${CONFIG_$1}" = xNo; then $1_INCLUDE=$(${CONFIG_$1} m4_default([$3],[--cflags])) - $1_INCLUDE=$(echo ${$1_INCLUDE} | $SED -e "s|-I|-I${$1_DIR}|g") + $1_INCLUDE=$(echo ${$1_INCLUDE} | $SED -e "s|-I|-I${$1_DIR}|g" -e "s|-std=c99||g") $1_LIB=$(${CONFIG_$1} m4_default([$4],[--libs])) $1_LIB=$(echo ${$1_LIB} | $SED -e "s|-L|-L${$1_DIR}|g") diff --git a/autoconf/ast_pkgconfig.m4 b/autoconf/ast_pkgconfig.m4 index ae7bbc086..3415ed547 100644 --- a/autoconf/ast_pkgconfig.m4 +++ b/autoconf/ast_pkgconfig.m4 @@ -5,7 +5,7 @@ AC_DEFUN([AST_PKG_CONFIG_CHECK], if test "x${PBX_$1}" != "x1" -a "${USE_$1}" != "no"; then PKG_CHECK_MODULES($1, $2, [ PBX_$1=1 - $1_INCLUDE="$$1_CFLAGS" + $1_INCLUDE=$(echo ${$1_CFLAGS} | $SED -e "s|-std=c99||g") $1_LIB="$$1_LIBS" AC_DEFINE([HAVE_$1], 1, [Define if your system has the $1 libraries.]) ], [ diff --git a/bridges/bridge_softmix.c b/bridges/bridge_softmix.c index 16e1fb897..f0a3fb42d 100644 --- a/bridges/bridge_softmix.c +++ b/bridges/bridge_softmix.c @@ -69,6 +69,15 @@ #define SOFTBRIDGE_VIDEO_DEST_LEN strlen(SOFTBRIDGE_VIDEO_DEST_PREFIX) #define SOFTBRIDGE_VIDEO_DEST_SEPARATOR '_' +struct softmix_remb_collector { + /*! The frame which will be given to each source stream */ + struct ast_frame frame; + /*! The REMB to send to the source which is collecting REMB reports */ + struct ast_rtp_rtcp_feedback feedback; + /*! The maximum bitrate */ + unsigned int bitrate; +}; + struct softmix_stats { /*! Each index represents a sample rate used above the internal rate. */ unsigned int sample_rates[16]; @@ -768,6 +777,10 @@ static void softmix_bridge_leave(struct ast_bridge *bridge, struct ast_bridge_ch ast_stream_topology_free(sc->topology); + ao2_cleanup(sc->remb_collector); + + AST_VECTOR_FREE(&sc->video_sources); + /* Drop mutex lock */ ast_mutex_destroy(&sc->lock); @@ -1160,6 +1173,39 @@ static int softmix_bridge_write_control(struct ast_bridge *bridge, struct ast_br /*! * \internal + * \brief Determine what to do with an RTCP frame. + * \since 15.4.0 + * + * \param bridge Which bridge is getting the frame + * \param bridge_channel Which channel is writing the frame. + * \param frame What is being written. + */ +static void softmix_bridge_write_rtcp(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame) +{ + struct ast_rtp_rtcp_feedback *feedback = frame->data.ptr; + struct softmix_channel *sc = bridge_channel->tech_pvt; + + /* We only care about REMB reports right now. In the future we may be able to use sender or + * receiver reports to further tweak things, but not yet. + */ + if (frame->subclass.integer != AST_RTP_RTCP_PSFB || feedback->fmt != AST_RTP_RTCP_FMT_REMB || + bridge->softmix.video_mode.mode != AST_BRIDGE_VIDEO_MODE_SFU || + !bridge->softmix.video_mode.mode_data.sfu_data.remb_send_interval) { + return; + } + + /* REMB is the total estimated maximum bitrate across all streams within the session, so we store + * only the latest report and use it everywhere. + */ + ast_mutex_lock(&sc->lock); + sc->remb = feedback->remb; + ast_mutex_unlock(&sc->lock); + + return; +} + +/*! + * \internal * \brief Determine what to do with a frame written into the bridge. * \since 12.0.0 * @@ -1204,6 +1250,9 @@ static int softmix_bridge_write(struct ast_bridge *bridge, struct ast_bridge_cha case AST_FRAME_CONTROL: res = softmix_bridge_write_control(bridge, bridge_channel, frame); break; + case AST_FRAME_RTCP: + softmix_bridge_write_rtcp(bridge, bridge_channel, frame); + break; case AST_FRAME_BRIDGE_ACTION: res = ast_bridge_queue_everyone_else(bridge, bridge_channel, frame); break; @@ -1219,6 +1268,104 @@ static int softmix_bridge_write(struct ast_bridge *bridge, struct ast_bridge_cha return res; } +static void remb_collect_report(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, + struct softmix_bridge_data *softmix_data, struct softmix_channel *sc) +{ + int i; + unsigned int bitrate; + + /* If there are no video sources that we are a receiver of then we have noone to + * report REMB to. + */ + if (!AST_VECTOR_SIZE(&sc->video_sources)) { + return; + } + + /* We evenly divide the available maximum bitrate across the video sources + * to this receiver so each source gets an equal slice. + */ + bitrate = (sc->remb.br_mantissa << sc->remb.br_exp) / AST_VECTOR_SIZE(&sc->video_sources); + + /* If this receiver has no bitrate yet ignore it */ + if (!bitrate) { + return; + } + + for (i = 0; i < AST_VECTOR_SIZE(&sc->video_sources); ++i) { + struct softmix_remb_collector *collector; + + /* The collector will always exist if a video source is in our list */ + collector = AST_VECTOR_GET(&softmix_data->remb_collectors, AST_VECTOR_GET(&sc->video_sources, i)); + + if (!collector->bitrate) { + collector->bitrate = bitrate; + continue; + } + + switch (bridge->softmix.video_mode.mode_data.sfu_data.remb_behavior) { + case AST_BRIDGE_VIDEO_SFU_REMB_AVERAGE: + collector->bitrate = (collector->bitrate + bitrate) / 2; + break; + case AST_BRIDGE_VIDEO_SFU_REMB_LOWEST: + if (bitrate < collector->bitrate) { + collector->bitrate = bitrate; + } + break; + case AST_BRIDGE_VIDEO_SFU_REMB_HIGHEST: + if (bitrate > collector->bitrate) { + collector->bitrate = bitrate; + } + break; + } + } +} + +static void remb_send_report(struct ast_bridge_channel *bridge_channel, struct softmix_channel *sc) +{ + int i; + + if (!sc->remb_collector) { + return; + } + + /* If we have a new bitrate then use it for the REMB, if not we use the previous + * one until we know otherwise. This way the bitrate doesn't drop to 0 all of a sudden. + */ + if (sc->remb_collector->bitrate) { + sc->remb_collector->feedback.remb.br_mantissa = sc->remb_collector->bitrate; + sc->remb_collector->feedback.remb.br_exp = 0; + + /* The mantissa only has 18 bits available, so while it exceeds them we bump + * up the exp. + */ + while (sc->remb_collector->feedback.remb.br_mantissa > 0x3ffff) { + sc->remb_collector->feedback.remb.br_mantissa = sc->remb_collector->feedback.remb.br_mantissa >> 1; + sc->remb_collector->feedback.remb.br_exp++; + } + } + + for (i = 0; i < AST_VECTOR_SIZE(&bridge_channel->stream_map.to_bridge); ++i) { + int bridge_num = AST_VECTOR_GET(&bridge_channel->stream_map.to_bridge, i); + + /* If this stream is not being provided to the bridge there can be no receivers of it + * so therefore no REMB reports. + */ + if (bridge_num == -1) { + continue; + } + + /* We need to update the frame with this stream, or else it won't be + * properly routed. We don't use the actual channel stream identifier as + * the bridging core will do the translation from bridge stream identifier to + * channel stream identifier. + */ + sc->remb_collector->frame.stream_num = bridge_num; + ast_bridge_channel_queue_frame(bridge_channel, &sc->remb_collector->frame); + } + + sc->remb_collector->bitrate = 0; +} + static void gather_softmix_stats(struct softmix_stats *stats, const struct softmix_bridge_data *softmix_data, struct ast_bridge_channel *bridge_channel) @@ -1440,6 +1587,7 @@ static int softmix_mixing_loop(struct ast_bridge *bridge) struct ast_format *cur_slin = ast_format_cache_get_slin_by_rate(softmix_data->internal_rate); unsigned int softmix_samples = SOFTMIX_SAMPLES(softmix_data->internal_rate, softmix_data->internal_mixing_interval); unsigned int softmix_datalen = SOFTMIX_DATALEN(softmix_data->internal_rate, softmix_data->internal_mixing_interval); + int remb_update = 0; if (softmix_datalen > MAX_DATALEN) { /* This should NEVER happen, but if it does we need to know about it. Almost @@ -1478,6 +1626,14 @@ static int softmix_mixing_loop(struct ast_bridge *bridge) check_binaural_position_change(bridge, softmix_data); #endif + /* If we need to do a REMB update to all video sources then do so */ + if (bridge->softmix.video_mode.mode == AST_BRIDGE_VIDEO_MODE_SFU && + bridge->softmix.video_mode.mode_data.sfu_data.remb_send_interval && + ast_tvdiff_ms(ast_tvnow(), softmix_data->last_remb_update) > bridge->softmix.video_mode.mode_data.sfu_data.remb_send_interval) { + remb_update = 1; + softmix_data->last_remb_update = ast_tvnow(); + } + /* Go through pulling audio from each factory that has it available */ AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) { struct softmix_channel *sc = bridge_channel->tech_pvt; @@ -1512,6 +1668,9 @@ static int softmix_mixing_loop(struct ast_bridge *bridge) #endif mixing_array.used_entries++; } + if (remb_update) { + remb_collect_report(bridge, bridge_channel, softmix_data, sc); + } ast_mutex_unlock(&sc->lock); } @@ -1562,6 +1721,10 @@ static int softmix_mixing_loop(struct ast_bridge *bridge) /* A frame is now ready for the channel. */ ast_bridge_channel_queue_frame(bridge_channel, &sc->write_frame); + + if (remb_update) { + remb_send_report(bridge_channel, sc); + } } update_all_rates = 0; @@ -1688,6 +1851,8 @@ static void softmix_bridge_data_destroy(struct softmix_bridge_data *softmix_data } ast_mutex_destroy(&softmix_data->lock); ast_cond_destroy(&softmix_data->cond); + AST_VECTOR_RESET(&softmix_data->remb_collectors, ao2_cleanup); + AST_VECTOR_FREE(&softmix_data->remb_collectors); ast_free(softmix_data); } @@ -1718,6 +1883,8 @@ static int softmix_bridge_create(struct ast_bridge *bridge) softmix_data->internal_mixing_interval); #endif + AST_VECTOR_INIT(&softmix_data->remb_collectors, 0); + bridge->tech_pvt = softmix_data; /* Start the mixing thread. */ @@ -1814,7 +1981,10 @@ static void map_source_to_destinations(const char *source_stream_name, const cha stream = ast_stream_topology_get_stream(topology, i); if (is_video_dest(stream, source_channel_name, source_stream_name)) { + struct softmix_channel *sc = participant->tech_pvt; + AST_VECTOR_REPLACE(&participant->stream_map.to_channel, bridge_stream_position, i); + AST_VECTOR_APPEND(&sc->video_sources, bridge_stream_position); break; } } @@ -1824,6 +1994,58 @@ static void map_source_to_destinations(const char *source_stream_name, const cha } /*! + * \brief Allocate a REMB collector + * + * \retval non-NULL success + * \retval NULL failure + */ +static struct softmix_remb_collector *remb_collector_alloc(void) +{ + struct softmix_remb_collector *collector; + + collector = ao2_alloc_options(sizeof(*collector), NULL, AO2_ALLOC_OPT_LOCK_NOLOCK); + if (!collector) { + return NULL; + } + + collector->frame.frametype = AST_FRAME_RTCP; + collector->frame.subclass.integer = AST_RTP_RTCP_PSFB; + collector->feedback.fmt = AST_RTP_RTCP_FMT_REMB; + collector->frame.data.ptr = &collector->feedback; + collector->frame.datalen = sizeof(collector->feedback); + + return collector; +} + +/*! + * \brief Setup REMB collection for a particular bridge stream and channel. + * + * \param bridge The bridge + * \param bridge_channel Channel that is collecting REMB information + * \param bridge_stream_position The slot in the bridge where source video comes from + */ +static void remb_enable_collection(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, + size_t bridge_stream_position) +{ + struct softmix_channel *sc = bridge_channel->tech_pvt; + struct softmix_bridge_data *softmix_data = bridge->tech_pvt; + + if (!sc->remb_collector) { + sc->remb_collector = remb_collector_alloc(); + if (!sc->remb_collector) { + /* This is not fatal. Things will still continue to work but we won't + * produce a REMB report to the sender. + */ + return; + } + } + + if (AST_VECTOR_REPLACE(&softmix_data->remb_collectors, bridge_stream_position, ao2_bump(sc->remb_collector))) { + ao2_ref(sc->remb_collector, -1); + } +} + +/*! * \brief stream_topology_changed callback * * For most video modes, nothing beyond the ordinary is required. @@ -1835,6 +2057,8 @@ static void map_source_to_destinations(const char *source_stream_name, const cha */ static void softmix_bridge_stream_topology_changed(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel) { + struct softmix_bridge_data *softmix_data = bridge->tech_pvt; + struct softmix_channel *sc; struct ast_bridge_channel *participant; struct ast_vector_int media_types; int nths[AST_MEDIA_TYPE_END] = {0}; @@ -1852,11 +2076,22 @@ static void softmix_bridge_stream_topology_changed(struct ast_bridge *bridge, st AST_VECTOR_INIT(&media_types, AST_MEDIA_TYPE_END); + /* The bridge stream identifiers may change, so reset the mapping for them. + * When channels end up getting added back in they'll reuse their existing + * collector and won't need to allocate a new one (unless they were just added). + */ + AST_VECTOR_RESET(&softmix_data->remb_collectors, ao2_cleanup); + /* First traversal: re-initialize all of the participants' stream maps */ AST_LIST_TRAVERSE(&bridge->channels, participant, entry) { ast_bridge_channel_lock(participant); + AST_VECTOR_RESET(&participant->stream_map.to_channel, AST_VECTOR_ELEM_CLEANUP_NOOP); AST_VECTOR_RESET(&participant->stream_map.to_bridge, AST_VECTOR_ELEM_CLEANUP_NOOP); + + sc = participant->tech_pvt; + AST_VECTOR_RESET(&sc->video_sources, AST_VECTOR_ELEM_CLEANUP_NOOP); + ast_bridge_channel_unlock(participant); } @@ -1897,7 +2132,12 @@ static void softmix_bridge_stream_topology_changed(struct ast_bridge *bridge, st if (is_video_source(stream)) { AST_VECTOR_APPEND(&media_types, AST_MEDIA_TYPE_VIDEO); AST_VECTOR_REPLACE(&participant->stream_map.to_bridge, i, AST_VECTOR_SIZE(&media_types) - 1); - AST_VECTOR_REPLACE(&participant->stream_map.to_channel, AST_VECTOR_SIZE(&media_types) - 1, -1); + /* + * There are cases where we need to bidirectionally send frames, such as for REMB reports + * so we also map back to the channel. + */ + AST_VECTOR_REPLACE(&participant->stream_map.to_channel, AST_VECTOR_SIZE(&media_types) - 1, i); + remb_enable_collection(bridge, participant, AST_VECTOR_SIZE(&media_types) - 1); /* * Unlock the channel and participant to prevent * potential deadlock in map_source_to_destinations(). diff --git a/bridges/bridge_softmix/include/bridge_softmix_internal.h b/bridges/bridge_softmix/include/bridge_softmix_internal.h index f842acb5e..3aa90915d 100644 --- a/bridges/bridge_softmix/include/bridge_softmix_internal.h +++ b/bridges/bridge_softmix/include/bridge_softmix_internal.h @@ -50,6 +50,8 @@ #include "asterisk/astobj2.h" #include "asterisk/timing.h" #include "asterisk/translate.h" +#include "asterisk/rtp_engine.h" +#include "asterisk/vector.h" #ifdef BINAURAL_RENDERING #include <fftw3.h> @@ -124,6 +126,8 @@ struct video_follow_talker_data { int energy_average; }; +struct softmix_remb_collector; + /*! \brief Structure which contains per-channel mixing information */ struct softmix_channel { /*! Lock to protect this structure */ @@ -169,6 +173,12 @@ struct softmix_channel { struct video_follow_talker_data video_talker; /*! The ideal stream topology for the channel */ struct ast_stream_topology *topology; + /*! The latest REMB report from this participant */ + struct ast_rtp_rtcp_feedback_remb remb; + /*! The REMB collector for this channel, collects REMB from all video receivers */ + struct softmix_remb_collector *remb_collector; + /*! The bridge streams which are feeding us video sources */ + AST_VECTOR(, int) video_sources; }; struct softmix_bridge_data { @@ -202,6 +212,10 @@ struct softmix_bridge_data { unsigned int binaural_init; /*! The last time a video update was sent into the bridge */ struct timeval last_video_update; + /*! The last time a REMB frame was sent to each source of video */ + struct timeval last_remb_update; + /*! Per-bridge stream REMB collectors, which flow back to video source */ + AST_VECTOR(, struct softmix_remb_collector *) remb_collectors; }; struct softmix_mixing_array { diff --git a/channels/chan_pjsip.c b/channels/chan_pjsip.c index 5cb52a5b2..dde7416c3 100644 --- a/channels/chan_pjsip.c +++ b/channels/chan_pjsip.c @@ -718,7 +718,7 @@ static int chan_pjsip_answer(struct ast_channel *ast) can occur between this thread and bridging (specifically when native bridging attempts to do direct media) */ ast_channel_unlock(ast); - res = ast_sip_push_task_synchronous(session->serializer, answer, session); + res = ast_sip_push_task_wait_serializer(session->serializer, answer, session); if (res) { if (res == -1) { ast_log(LOG_ERROR,"Cannot answer '%s': Unable to push answer task to the threadpool.\n", @@ -966,6 +966,16 @@ static int chan_pjsip_write_stream(struct ast_channel *ast, int stream_num, stru case AST_FRAME_CNG: break; case AST_FRAME_RTCP: + /* We only support writing out feedback */ + if (frame->subclass.integer != AST_RTP_RTCP_PSFB || !media) { + return 0; + } else if (media->type != AST_MEDIA_TYPE_VIDEO) { + ast_debug(3, "Channel %s stream %d is of type '%s', not video! Unable to write RTCP feedback.\n", + ast_channel_name(ast), stream_num, ast_codec_media_type2str(media->type)); + return 0; + } else if (media->write_callback) { + res = media->write_callback(session, media, frame); + } break; default: ast_log(LOG_WARNING, "Can't send %u type frames with PJSIP\n", frame->frametype); @@ -2492,10 +2502,10 @@ static struct ast_channel *chan_pjsip_request_with_stream_topology(const char *t req_data.topology = topology; req_data.dest = data; - /* Default failure value in case ast_sip_push_task_synchronous() itself fails. */ + /* Default failure value in case ast_sip_push_task_wait_servant() itself fails. */ req_data.cause = AST_CAUSE_FAILURE; - if (ast_sip_push_task_synchronous(NULL, request, &req_data)) { + if (ast_sip_push_task_wait_servant(NULL, request, &req_data)) { *cause = req_data.cause; return NULL; } diff --git a/channels/chan_sip.c b/channels/chan_sip.c index 138021e82..46f9ad699 100644 --- a/channels/chan_sip.c +++ b/channels/chan_sip.c @@ -25856,7 +25856,7 @@ static int handle_invite_replaces(struct sip_pvt *p, struct sip_request *req, int *nounlock, struct sip_pvt *replaces_pvt, struct ast_channel *replaces_chan) { struct ast_channel *c; - RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup); + struct ast_bridge *bridge; if (req->ignore) { return 0; @@ -25872,6 +25872,7 @@ static int handle_invite_replaces(struct sip_pvt *p, struct sip_request *req, } append_history(p, "Xfer", "INVITE/Replace received"); + /* Get a ref to ensure the channel cannot go away on us. */ c = ast_channel_ref(p->owner); /* Fake call progress */ @@ -25886,21 +25887,24 @@ static int handle_invite_replaces(struct sip_pvt *p, struct sip_request *req, ast_raw_answer(c); - ast_channel_lock(replaces_chan); - bridge = ast_channel_get_bridge(replaces_chan); - ast_channel_unlock(replaces_chan); - + bridge = ast_bridge_transfer_acquire_bridge(replaces_chan); if (bridge) { + /* + * We have two refs of the channel. One is held in c and the other + * is notionally represented by p->owner. The impart is "stealing" + * the p->owner ref on success so the bridging system can have + * control of when the channel is hung up. + */ if (ast_bridge_impart(bridge, c, replaces_chan, NULL, AST_BRIDGE_IMPART_CHAN_INDEPENDENT)) { ast_hangup(c); - ast_channel_unref(c); } + ao2_ref(bridge, -1); } else { ast_channel_move(replaces_chan, c); ast_hangup(c); - ast_channel_unref(c); } + ast_channel_unref(c); sip_pvt_lock(p); return 0; } diff --git a/channels/pjsip/dialplan_functions.c b/channels/pjsip/dialplan_functions.c index aa376f892..ce347dcd9 100644 --- a/channels/pjsip/dialplan_functions.c +++ b/channels/pjsip/dialplan_functions.c @@ -897,7 +897,7 @@ int pjsip_acf_channel_read(struct ast_channel *chan, const char *cmd, char *data func_args.field = args.field; func_args.buf = buf; func_args.len = len; - if (ast_sip_push_task_synchronous(func_args.session->serializer, read_pjsip, &func_args)) { + if (ast_sip_push_task_wait_serializer(func_args.session->serializer, read_pjsip, &func_args)) { ast_log(LOG_WARNING, "Unable to read properties of channel %s: failed to push task\n", ast_channel_name(chan)); ao2_ref(func_args.session, -1); return -1; @@ -1219,7 +1219,7 @@ int pjsip_acf_media_offer_write(struct ast_channel *chan, const char *cmd, char mdata.media_type = AST_MEDIA_TYPE_VIDEO; } - return ast_sip_push_task_synchronous(channel->session->serializer, media_offer_write_av, &mdata); + return ast_sip_push_task_wait_serializer(channel->session->serializer, media_offer_write_av, &mdata); } int pjsip_acf_dtmf_mode_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len) @@ -1390,7 +1390,7 @@ int pjsip_acf_dtmf_mode_write(struct ast_channel *chan, const char *cmd, char *d ast_channel_unlock(chan); - return ast_sip_push_task_synchronous(channel->session->serializer, dtmf_mode_refresh_cb, &rdata); + return ast_sip_push_task_wait_serializer(channel->session->serializer, dtmf_mode_refresh_cb, &rdata); } static int refresh_write_cb(void *obj) @@ -1438,5 +1438,5 @@ int pjsip_acf_session_refresh_write(struct ast_channel *chan, const char *cmd, c rdata.method = AST_SIP_SESSION_REFRESH_METHOD_UPDATE; } - return ast_sip_push_task_synchronous(channel->session->serializer, refresh_write_cb, &rdata); + return ast_sip_push_task_wait_serializer(channel->session->serializer, refresh_write_cb, &rdata); } diff --git a/configs/samples/confbridge.conf.sample b/configs/samples/confbridge.conf.sample index e2d8a620d..8b276cdb8 100644 --- a/configs/samples/confbridge.conf.sample +++ b/configs/samples/confbridge.conf.sample @@ -235,6 +235,14 @@ type=bridge ; the video stream. Since a full frame can be large limiting how often they occur can ; reduce bandwidth usage at the cost of increasing how long it may take a newly joined ; channel to receive the video stream. +;remb_send_interval=1000 ; Interval (in milliseconds) at which a combined REMB frame will be sent to sources of video. + ; A REMB frame contains receiver estimated maximum bitrate information. By creating a combined + ; frame and sending it to the sources of video the sender can be influenced on what bitrate + ; they choose allowing a better experience for the receivers. This defaults to 0, or disabled. +;remb_behavior=average ; How the combined REMB report for an SFU video bridge is constructed. If set to "average" then + ; the estimated maximum bitrate of each receiver is used to construct an average bitrate. If + ; set to "lowest" the lowest maximum bitrate is forwarded to the sender. If set to "highest" + ; the highest maximum bitrate is forwarded to the sender. This defaults to "average". ; All sounds in the conference are customizable using the bridge profile options below. ; Simply state the option followed by the filename or full path of the filename after @@ -7792,7 +7792,7 @@ if test "${WGET}" != ":" ; then DOWNLOAD=${WGET} DOWNLOAD_TO_STDOUT="${WGET} -q -O-" DOWNLOAD_TIMEOUT='--timeout=$1' -else if test "${CURL}" != ":" ; then +elif test "${CURL}" != ":" ; then DOWNLOAD="${CURL} -O --progress-bar -w \"%{url_effective}\n\"" DOWNLOAD_TO_STDOUT="${CURL} -Ls" DOWNLOAD_TIMEOUT='--max-time $(or $2,$1)' @@ -7844,7 +7844,6 @@ fi DOWNLOAD_TIMEOUT='--timeout=$(or $2,$1)' fi fi -fi @@ -13428,7 +13427,7 @@ else $as_echo "yes" >&6; } PBX_LIBEDIT=1 - LIBEDIT_INCLUDE="$LIBEDIT_CFLAGS" + LIBEDIT_INCLUDE=$(echo ${LIBEDIT_CFLAGS} | $SED -e "s|-std=c99||g") LIBEDIT_LIB="$LIBEDIT_LIBS" $as_echo "#define HAVE_LIBEDIT 1" >>confdefs.h @@ -14102,7 +14101,7 @@ fi if test ! "x${CONFIG_LIBXML2}" = xNo; then LIBXML2_INCLUDE=$(${CONFIG_LIBXML2} --cflags) - LIBXML2_INCLUDE=$(echo ${LIBXML2_INCLUDE} | $SED -e "s|-I|-I${LIBXML2_DIR}|g") + LIBXML2_INCLUDE=$(echo ${LIBXML2_INCLUDE} | $SED -e "s|-I|-I${LIBXML2_DIR}|g" -e "s|-std=c99||g") LIBXML2_LIB=$(${CONFIG_LIBXML2} --libs) LIBXML2_LIB=$(echo ${LIBXML2_LIB} | $SED -e "s|-L|-L${LIBXML2_DIR}|g") @@ -19202,15 +19201,15 @@ if test $ac_cv_sizeof_int = $ac_cv_sizeof_fd_set_fds_bits; then $as_echo "#define TYPEOF_FD_SET_FDS_BITS int" >>confdefs.h -else if test $ac_cv_sizeof_long = $ac_cv_sizeof_fd_set_fds_bits; then +elif test $ac_cv_sizeof_long = $ac_cv_sizeof_fd_set_fds_bits; then $as_echo "#define TYPEOF_FD_SET_FDS_BITS long" >>confdefs.h -else if test $ac_cv_sizeof_long_long = $ac_cv_sizeof_fd_set_fds_bits; then +elif test $ac_cv_sizeof_long_long = $ac_cv_sizeof_fd_set_fds_bits; then $as_echo "#define TYPEOF_FD_SET_FDS_BITS long long" >>confdefs.h -fi ; fi ; fi +fi { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dladdr in dlfcn.h" >&5 $as_echo_n "checking for dladdr in dlfcn.h... " >&6; } @@ -20374,15 +20373,13 @@ cat >>confdefs.h <<_ACEOF _ACEOF GSM_OK=1 - else - if test "${GSM_GSM_HEADER_FOUND}" = "1" ; then + elif test "${GSM_GSM_HEADER_FOUND}" = "1" ; then cat >>confdefs.h <<_ACEOF #define HAVE_GSM_GSM_HEADER 1 _ACEOF - GSM_OK=1 - fi + GSM_OK=1 fi if test "${GSM_OK}" = "1" ; then GSM_LIB="-lgsm" @@ -20494,7 +20491,7 @@ else $as_echo "yes" >&6; } PBX_ILBC=1 - ILBC_INCLUDE="$ILBC_CFLAGS" + ILBC_INCLUDE=$(echo ${ILBC_CFLAGS} | $SED -e "s|-std=c99||g") ILBC_LIB="$ILBC_LIBS" $as_echo "#define HAVE_ILBC 1" >>confdefs.h @@ -22632,7 +22629,7 @@ fi if test ! "x${CONFIG_MYSQLCLIENT}" = xNo; then MYSQLCLIENT_INCLUDE=$(${CONFIG_MYSQLCLIENT} --cflags) - MYSQLCLIENT_INCLUDE=$(echo ${MYSQLCLIENT_INCLUDE} | $SED -e "s|-I|-I${MYSQLCLIENT_DIR}|g") + MYSQLCLIENT_INCLUDE=$(echo ${MYSQLCLIENT_INCLUDE} | $SED -e "s|-I|-I${MYSQLCLIENT_DIR}|g" -e "s|-std=c99||g") MYSQLCLIENT_LIB=$(${CONFIG_MYSQLCLIENT} --libs) MYSQLCLIENT_LIB=$(echo ${MYSQLCLIENT_LIB} | $SED -e "s|-L|-L${MYSQLCLIENT_DIR}|g") @@ -22849,7 +22846,7 @@ fi if test ! "x${CONFIG_NEON}" = xNo; then NEON_INCLUDE=$(${CONFIG_NEON} --cflags) - NEON_INCLUDE=$(echo ${NEON_INCLUDE} | $SED -e "s|-I|-I${NEON_DIR}|g") + NEON_INCLUDE=$(echo ${NEON_INCLUDE} | $SED -e "s|-I|-I${NEON_DIR}|g" -e "s|-std=c99||g") NEON_LIB=$(${CONFIG_NEON} --libs) NEON_LIB=$(echo ${NEON_LIB} | $SED -e "s|-L|-L${NEON_DIR}|g") @@ -22969,7 +22966,7 @@ fi if test ! "x${CONFIG_NEON29}" = xNo; then NEON29_INCLUDE=$(${CONFIG_NEON29} --cflags) - NEON29_INCLUDE=$(echo ${NEON29_INCLUDE} | $SED -e "s|-I|-I${NEON29_DIR}|g") + NEON29_INCLUDE=$(echo ${NEON29_INCLUDE} | $SED -e "s|-I|-I${NEON29_DIR}|g" -e "s|-std=c99||g") NEON29_LIB=$(${CONFIG_NEON29} --libs) NEON29_LIB=$(echo ${NEON29_LIB} | $SED -e "s|-L|-L${NEON29_DIR}|g") @@ -23113,7 +23110,7 @@ fi if test ! "x${CONFIG_NETSNMP}" = xNo; then NETSNMP_INCLUDE=$(${CONFIG_NETSNMP} --cflags) - NETSNMP_INCLUDE=$(echo ${NETSNMP_INCLUDE} | $SED -e "s|-I|-I${NETSNMP_DIR}|g") + NETSNMP_INCLUDE=$(echo ${NETSNMP_INCLUDE} | $SED -e "s|-I|-I${NETSNMP_DIR}|g" -e "s|-std=c99||g") NETSNMP_LIB=$(${CONFIG_NETSNMP} --agent-libs) NETSNMP_LIB=$(echo ${NETSNMP_LIB} | $SED -e "s|-L|-L${NETSNMP_DIR}|g") @@ -24599,7 +24596,7 @@ else $as_echo "yes" >&6; } PBX_PJPROJECT=1 - PJPROJECT_INCLUDE="$PJPROJECT_CFLAGS" + PJPROJECT_INCLUDE=$(echo ${PJPROJECT_CFLAGS} | $SED -e "s|-std=c99||g") PJPROJECT_LIB="$PJPROJECT_LIBS" $as_echo "#define HAVE_PJPROJECT 1" >>confdefs.h @@ -25653,7 +25650,7 @@ else $as_echo "yes" >&6; } PBX_PYTHONDEV=1 - PYTHONDEV_INCLUDE="$PYTHONDEV_CFLAGS" + PYTHONDEV_INCLUDE=$(echo ${PYTHONDEV_CFLAGS} | $SED -e "s|-std=c99||g") PYTHONDEV_LIB="$PYTHONDEV_LIBS" $as_echo "#define HAVE_PYTHONDEV 1" >>confdefs.h @@ -25839,7 +25836,7 @@ else $as_echo "yes" >&6; } PBX_PORTAUDIO=1 - PORTAUDIO_INCLUDE="$PORTAUDIO_CFLAGS" + PORTAUDIO_INCLUDE=$(echo ${PORTAUDIO_CFLAGS} | $SED -e "s|-std=c99||g") PORTAUDIO_LIB="$PORTAUDIO_LIBS" $as_echo "#define HAVE_PORTAUDIO 1" >>confdefs.h @@ -28898,6 +28895,7 @@ fi +for ver in 5.3 5.2 5.1; do if test "x${PBX_LUA}" != "x1" -a "${USE_LUA}" != "no"; then pbxlibdir="" @@ -28912,117 +28910,14 @@ if test "x${PBX_LUA}" != "x1" -a "${USE_LUA}" != "no"; then ast_ext_lib_check_save_CFLAGS="${CFLAGS}" CFLAGS="${CFLAGS} " - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for luaL_newstate in -llua5.3" >&5 -$as_echo_n "checking for luaL_newstate in -llua5.3... " >&6; } -if ${ac_cv_lib_lua5_3_luaL_newstate+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_check_lib_save_LIBS=$LIBS -LIBS="-llua5.3 ${pbxlibdir} -lm $LIBS" -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -/* Override any GCC internal prototype to avoid an error. - Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -#ifdef __cplusplus -extern "C" -#endif -char luaL_newstate (); -int -main () -{ -return luaL_newstate (); - ; - return 0; -} -_ACEOF -if ac_fn_c_try_link "$LINENO"; then : - ac_cv_lib_lua5_3_luaL_newstate=yes -else - ac_cv_lib_lua5_3_luaL_newstate=no -fi -rm -f core conftest.err conftest.$ac_objext \ - conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_lua5_3_luaL_newstate" >&5 -$as_echo "$ac_cv_lib_lua5_3_luaL_newstate" >&6; } -if test "x$ac_cv_lib_lua5_3_luaL_newstate" = xyes; then : - AST_LUA_FOUND=yes -else - AST_LUA_FOUND=no -fi - - CFLAGS="${ast_ext_lib_check_save_CFLAGS}" - - - # now check for the header. - if test "${AST_LUA_FOUND}" = "yes"; then - LUA_LIB="${pbxlibdir} -llua5.3 -lm" - # if --with-LUA=DIR has been specified, use it. - if test "x${LUA_DIR}" != "x"; then - LUA_INCLUDE="-I${LUA_DIR}/include" - fi - LUA_INCLUDE="${LUA_INCLUDE} " - - # check for the header - ast_ext_lib_check_saved_CPPFLAGS="${CPPFLAGS}" - CPPFLAGS="${CPPFLAGS} ${LUA_INCLUDE}" - ac_fn_c_check_header_mongrel "$LINENO" "lua5.3/lua.h" "ac_cv_header_lua5_3_lua_h" "$ac_includes_default" -if test "x$ac_cv_header_lua5_3_lua_h" = xyes; then : - LUA_HEADER_FOUND=1 -else - LUA_HEADER_FOUND=0 -fi - - - CPPFLAGS="${ast_ext_lib_check_saved_CPPFLAGS}" - - if test "x${LUA_HEADER_FOUND}" = "x0" ; then - LUA_LIB="" - LUA_INCLUDE="" - else - - PBX_LUA=1 - cat >>confdefs.h <<_ACEOF -#define HAVE_LUA 1 -_ACEOF - - fi - fi -fi - - -if test "x${PBX_LUA}" = "x1" ; then - if test x"${LUA_DIR}" = x; then - LUA_INCLUDE="${LUA_INCLUDE} -I/usr/include/lua5.3" - else - LUA_INCLUDE="${LUA_INCLUDE} -I${LUA_DIR}/lua5.3" - fi -fi - - -if test "x${PBX_LUA}" != "x1" -a "${USE_LUA}" != "no"; then - pbxlibdir="" - # if --with-LUA=DIR has been specified, use it. - if test "x${LUA_DIR}" != "x"; then - if test -d ${LUA_DIR}/lib; then - pbxlibdir="-L${LUA_DIR}/lib" - else - pbxlibdir="-L${LUA_DIR}" - fi - fi - - ast_ext_lib_check_save_CFLAGS="${CFLAGS}" - CFLAGS="${CFLAGS} " - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for luaL_newstate in -llua5.2" >&5 -$as_echo_n "checking for luaL_newstate in -llua5.2... " >&6; } -if ${ac_cv_lib_lua5_2_luaL_newstate+:} false; then : + as_ac_Lib=`$as_echo "ac_cv_lib_lua${ver}''_luaL_newstate" | $as_tr_sh` +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for luaL_newstate in -llua${ver}" >&5 +$as_echo_n "checking for luaL_newstate in -llua${ver}... " >&6; } +if eval \${$as_ac_Lib+:} false; then : $as_echo_n "(cached) " >&6 else ac_check_lib_save_LIBS=$LIBS -LIBS="-llua5.2 ${pbxlibdir} -lm $LIBS" +LIBS="-llua${ver} ${pbxlibdir} -lm $LIBS" cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ @@ -29042,121 +28937,18 @@ return luaL_newstate (); } _ACEOF if ac_fn_c_try_link "$LINENO"; then : - ac_cv_lib_lua5_2_luaL_newstate=yes + eval "$as_ac_Lib=yes" else - ac_cv_lib_lua5_2_luaL_newstate=no + eval "$as_ac_Lib=no" fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext LIBS=$ac_check_lib_save_LIBS fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_lua5_2_luaL_newstate" >&5 -$as_echo "$ac_cv_lib_lua5_2_luaL_newstate" >&6; } -if test "x$ac_cv_lib_lua5_2_luaL_newstate" = xyes; then : - AST_LUA_FOUND=yes -else - AST_LUA_FOUND=no -fi - - CFLAGS="${ast_ext_lib_check_save_CFLAGS}" - - - # now check for the header. - if test "${AST_LUA_FOUND}" = "yes"; then - LUA_LIB="${pbxlibdir} -llua5.2 -lm" - # if --with-LUA=DIR has been specified, use it. - if test "x${LUA_DIR}" != "x"; then - LUA_INCLUDE="-I${LUA_DIR}/include" - fi - LUA_INCLUDE="${LUA_INCLUDE} " - - # check for the header - ast_ext_lib_check_saved_CPPFLAGS="${CPPFLAGS}" - CPPFLAGS="${CPPFLAGS} ${LUA_INCLUDE}" - ac_fn_c_check_header_mongrel "$LINENO" "lua5.2/lua.h" "ac_cv_header_lua5_2_lua_h" "$ac_includes_default" -if test "x$ac_cv_header_lua5_2_lua_h" = xyes; then : - LUA_HEADER_FOUND=1 -else - LUA_HEADER_FOUND=0 -fi - - - CPPFLAGS="${ast_ext_lib_check_saved_CPPFLAGS}" - - if test "x${LUA_HEADER_FOUND}" = "x0" ; then - LUA_LIB="" - LUA_INCLUDE="" - else - - PBX_LUA=1 - cat >>confdefs.h <<_ACEOF -#define HAVE_LUA 1 -_ACEOF - - fi - fi -fi - - -if test "x${PBX_LUA}" = "x1" ; then - if test x"${LUA_DIR}" = x; then - LUA_INCLUDE="${LUA_INCLUDE} -I/usr/include/lua5.2" - else - LUA_INCLUDE="${LUA_INCLUDE} -I${LUA_DIR}/lua5.2" - fi -fi - - -if test "x${PBX_LUA}" != "x1" -a "${USE_LUA}" != "no"; then - pbxlibdir="" - # if --with-LUA=DIR has been specified, use it. - if test "x${LUA_DIR}" != "x"; then - if test -d ${LUA_DIR}/lib; then - pbxlibdir="-L${LUA_DIR}/lib" - else - pbxlibdir="-L${LUA_DIR}" - fi - fi - - ast_ext_lib_check_save_CFLAGS="${CFLAGS}" - CFLAGS="${CFLAGS} " - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for luaL_newstate in -llua5.1" >&5 -$as_echo_n "checking for luaL_newstate in -llua5.1... " >&6; } -if ${ac_cv_lib_lua5_1_luaL_newstate+:} false; then : - $as_echo_n "(cached) " >&6 -else - ac_check_lib_save_LIBS=$LIBS -LIBS="-llua5.1 ${pbxlibdir} -lm $LIBS" -cat confdefs.h - <<_ACEOF >conftest.$ac_ext -/* end confdefs.h. */ - -/* Override any GCC internal prototype to avoid an error. - Use char because int might match the return type of a GCC - builtin and then its argument prototype would still apply. */ -#ifdef __cplusplus -extern "C" -#endif -char luaL_newstate (); -int -main () -{ -return luaL_newstate (); - ; - return 0; -} -_ACEOF -if ac_fn_c_try_link "$LINENO"; then : - ac_cv_lib_lua5_1_luaL_newstate=yes -else - ac_cv_lib_lua5_1_luaL_newstate=no -fi -rm -f core conftest.err conftest.$ac_objext \ - conftest$ac_exeext conftest.$ac_ext -LIBS=$ac_check_lib_save_LIBS -fi -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_lua5_1_luaL_newstate" >&5 -$as_echo "$ac_cv_lib_lua5_1_luaL_newstate" >&6; } -if test "x$ac_cv_lib_lua5_1_luaL_newstate" = xyes; then : +eval ac_res=\$$as_ac_Lib + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5 +$as_echo "$ac_res" >&6; } +if eval test \"x\$"$as_ac_Lib"\" = x"yes"; then : AST_LUA_FOUND=yes else AST_LUA_FOUND=no @@ -29167,7 +28959,7 @@ fi # now check for the header. if test "${AST_LUA_FOUND}" = "yes"; then - LUA_LIB="${pbxlibdir} -llua5.1 -lm" + LUA_LIB="${pbxlibdir} -llua${ver} -lm" # if --with-LUA=DIR has been specified, use it. if test "x${LUA_DIR}" != "x"; then LUA_INCLUDE="-I${LUA_DIR}/include" @@ -29177,8 +28969,9 @@ fi # check for the header ast_ext_lib_check_saved_CPPFLAGS="${CPPFLAGS}" CPPFLAGS="${CPPFLAGS} ${LUA_INCLUDE}" - ac_fn_c_check_header_mongrel "$LINENO" "lua5.1/lua.h" "ac_cv_header_lua5_1_lua_h" "$ac_includes_default" -if test "x$ac_cv_header_lua5_1_lua_h" = xyes; then : + as_ac_Header=`$as_echo "ac_cv_header_lua${ver}/lua.h" | $as_tr_sh` +ac_fn_c_check_header_mongrel "$LINENO" "lua${ver}/lua.h" "$as_ac_Header" "$ac_includes_default" +if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : LUA_HEADER_FOUND=1 else LUA_HEADER_FOUND=0 @@ -29202,13 +28995,15 @@ _ACEOF fi -if test "x${PBX_LUA}" = "x1" ; then - if test x"${LUA_DIR}" = x; then - LUA_INCLUDE="${LUA_INCLUDE} -I/usr/include/lua5.1" - else - LUA_INCLUDE="${LUA_INCLUDE} -I${LUA_DIR}/lua5.1" + if test "x${PBX_LUA}" = "x1" ; then + if test x"${LUA_DIR}" = x; then + LUA_INCLUDE="${LUA_INCLUDE} -I/usr/include/lua${ver}" + else + LUA_INCLUDE="${LUA_INCLUDE} -I${LUA_DIR}/lua${ver}" + fi + break; fi -fi +done # Some distributions (like openSUSE and NetBSD) remove the 5.x suffix. @@ -32345,7 +32140,7 @@ else $as_echo "yes" >&6; } PBX_GMIME=1 - GMIME_INCLUDE="$GMIME_CFLAGS" + GMIME_INCLUDE=$(echo ${GMIME_CFLAGS} | $SED -e "s|-std=c99||g") GMIME_LIB="$GMIME_LIBS" $as_echo "#define HAVE_GMIME 1" >>confdefs.h @@ -33254,7 +33049,7 @@ fi if test ! "x${CONFIG_SDL}" = xNo; then SDL_INCLUDE=$(${CONFIG_SDL} --cflags) - SDL_INCLUDE=$(echo ${SDL_INCLUDE} | $SED -e "s|-I|-I${SDL_DIR}|g") + SDL_INCLUDE=$(echo ${SDL_INCLUDE} | $SED -e "s|-I|-I${SDL_DIR}|g" -e "s|-std=c99||g") SDL_LIB=$(${CONFIG_SDL} --libs) SDL_LIB=$(echo ${SDL_LIB} | $SED -e "s|-L|-L${SDL_DIR}|g") @@ -33782,7 +33577,7 @@ else $as_echo "yes" >&6; } PBX_GTK2=1 - GTK2_INCLUDE="$GTK2_CFLAGS" + GTK2_INCLUDE=$(echo ${GTK2_CFLAGS} | $SED -e "s|-std=c99||g") GTK2_LIB="$GTK2_LIBS" $as_echo "#define HAVE_GTK2 1" >>confdefs.h @@ -33893,7 +33688,7 @@ else $as_echo "yes" >&6; } PBX_SYSTEMD=1 - SYSTEMD_INCLUDE="$SYSTEMD_CFLAGS" + SYSTEMD_INCLUDE=$(echo ${SYSTEMD_CFLAGS} | $SED -e "s|-std=c99||g") SYSTEMD_LIB="$SYSTEMD_LIBS" $as_echo "#define HAVE_SYSTEMD 1" >>confdefs.h @@ -35772,9 +35567,9 @@ fi ${ac_cv_path_EGREP} 'CURSES|GTK2|OSARCH|NEWT' makeopts > makeopts.acbak2 if test "x${ac_cv_path_CMP}" = "x:"; then ( cd `pwd`/menuselect && ./configure ) -else if ${ac_cv_path_CMP} -s makeopts.acbak makeopts.acbak2; then : ; else +elif ${ac_cv_path_CMP} -s makeopts.acbak makeopts.acbak2; then : ; else ( cd `pwd`/menuselect && ./configure ) -fi ; fi +fi rm makeopts.acbak makeopts.acbak2 diff --git a/configure.ac b/configure.ac index 702ff4f97..128b0a0f8 100644 --- a/configure.ac +++ b/configure.ac @@ -293,7 +293,7 @@ if test "${WGET}" != ":" ; then DOWNLOAD=${WGET} DOWNLOAD_TO_STDOUT="${WGET} -q -O-" DOWNLOAD_TIMEOUT='--timeout=$1' -else if test "${CURL}" != ":" ; then +elif test "${CURL}" != ":" ; then DOWNLOAD="${CURL} -O --progress-bar -w \"%{url_effective}\n\"" DOWNLOAD_TO_STDOUT="${CURL} -Ls" DOWNLOAD_TIMEOUT='--max-time $(or $2,$1)' @@ -305,7 +305,6 @@ else DOWNLOAD_TIMEOUT='--timeout=$(or $2,$1)' fi fi -fi AC_SUBST(DOWNLOAD) AC_SUBST(DOWNLOAD_TO_STDOUT) AC_SUBST(DOWNLOAD_TIMEOUT) @@ -1426,11 +1425,11 @@ AC_CHECK_SIZEOF(fd_set.fds_bits) # correctly if the size is wrong. if test $ac_cv_sizeof_int = $ac_cv_sizeof_fd_set_fds_bits; then AC_DEFINE([TYPEOF_FD_SET_FDS_BITS], [int], [Define to a type of the same size as fd_set.fds_bits[[0]]]) -else if test $ac_cv_sizeof_long = $ac_cv_sizeof_fd_set_fds_bits; then +elif test $ac_cv_sizeof_long = $ac_cv_sizeof_fd_set_fds_bits; then AC_DEFINE([TYPEOF_FD_SET_FDS_BITS], [long], [Define to a type of the same size as fd_set.fds_bits[[0]]]) -else if test $ac_cv_sizeof_long_long = $ac_cv_sizeof_fd_set_fds_bits; then +elif test $ac_cv_sizeof_long_long = $ac_cv_sizeof_fd_set_fds_bits; then AC_DEFINE([TYPEOF_FD_SET_FDS_BITS], [long long], [Define to a type of the same size as fd_set.fds_bits[[0]]]) -fi ; fi ; fi +fi AC_MSG_CHECKING(for dladdr in dlfcn.h) PBX_DLADDR=0 @@ -1542,11 +1541,9 @@ if test "${USE_GSM}" != "no"; then if test "${GSM_HEADER_FOUND}" = "1" ; then AC_DEFINE_UNQUOTED([HAVE_GSM_HEADER], 1, [Define to indicate that gsm.h has no prefix for its location]) GSM_OK=1 - else - if test "${GSM_GSM_HEADER_FOUND}" = "1" ; then - AC_DEFINE_UNQUOTED([HAVE_GSM_GSM_HEADER], 1, [Define to indicate that gsm.h is in gsm/gsm.h]) - GSM_OK=1 - fi + elif test "${GSM_GSM_HEADER_FOUND}" = "1" ; then + AC_DEFINE_UNQUOTED([HAVE_GSM_GSM_HEADER], 1, [Define to indicate that gsm.h is in gsm/gsm.h]) + GSM_OK=1 fi if test "${GSM_OK}" = "1" ; then GSM_LIB="-lgsm" @@ -2357,32 +2354,17 @@ if test -z "$__opus_include" -o x"$__opus_include" = x" " ; then fi AST_EXT_LIB_CHECK([OPUSFILE], [opusfile], [op_open_callbacks], [opus/opusfile.h], [], [$__opus_include]) -AST_EXT_LIB_CHECK([LUA], [lua5.3], [luaL_newstate], [lua5.3/lua.h], [-lm]) -if test "x${PBX_LUA}" = "x1" ; then - if test x"${LUA_DIR}" = x; then - LUA_INCLUDE="${LUA_INCLUDE} -I/usr/include/lua5.3" - else - LUA_INCLUDE="${LUA_INCLUDE} -I${LUA_DIR}/lua5.3" - fi -fi - -AST_EXT_LIB_CHECK([LUA], [lua5.2], [luaL_newstate], [lua5.2/lua.h], [-lm]) -if test "x${PBX_LUA}" = "x1" ; then - if test x"${LUA_DIR}" = x; then - LUA_INCLUDE="${LUA_INCLUDE} -I/usr/include/lua5.2" - else - LUA_INCLUDE="${LUA_INCLUDE} -I${LUA_DIR}/lua5.2" - fi -fi - -AST_EXT_LIB_CHECK([LUA], [lua5.1], [luaL_newstate], [lua5.1/lua.h], [-lm]) -if test "x${PBX_LUA}" = "x1" ; then - if test x"${LUA_DIR}" = x; then - LUA_INCLUDE="${LUA_INCLUDE} -I/usr/include/lua5.1" - else - LUA_INCLUDE="${LUA_INCLUDE} -I${LUA_DIR}/lua5.1" +for ver in 5.3 5.2 5.1; do + AST_EXT_LIB_CHECK([LUA], lua${ver}, [luaL_newstate], lua${ver}/lua.h, [-lm]) + if test "x${PBX_LUA}" = "x1" ; then + if test x"${LUA_DIR}" = x; then + LUA_INCLUDE="${LUA_INCLUDE} -I/usr/include/lua${ver}" + else + LUA_INCLUDE="${LUA_INCLUDE} -I${LUA_DIR}/lua${ver}" + fi + break; fi -fi +done # Some distributions (like openSUSE and NetBSD) remove the 5.x suffix. AST_EXT_LIB_CHECK([LUA], [lua], [luaL_newstate], [lua.h], [-lm]) @@ -2733,9 +2715,9 @@ AC_OUTPUT ${ac_cv_path_EGREP} 'CURSES|GTK2|OSARCH|NEWT' makeopts > makeopts.acbak2 if test "x${ac_cv_path_CMP}" = "x:"; then ( cd `pwd`/menuselect && ./configure ) -else if ${ac_cv_path_CMP} -s makeopts.acbak makeopts.acbak2; then : ; else +elif ${ac_cv_path_CMP} -s makeopts.acbak makeopts.acbak2; then : ; else ( cd `pwd`/menuselect && ./configure ) -fi ; fi +fi rm makeopts.acbak makeopts.acbak2 diff --git a/include/asterisk/bridge.h b/include/asterisk/bridge.h index 8d5c50211..3584085af 100644 --- a/include/asterisk/bridge.h +++ b/include/asterisk/bridge.h @@ -126,6 +126,24 @@ struct ast_bridge_video_talker_src_data { struct ast_channel *chan_old_vsrc; }; +/*! \brief REMB report behaviors */ +enum ast_bridge_video_sfu_remb_behavior { + /*! The average of all reports is sent to the sender */ + AST_BRIDGE_VIDEO_SFU_REMB_AVERAGE = 0, + /*! The lowest reported bitrate is forwarded to the sender */ + AST_BRIDGE_VIDEO_SFU_REMB_LOWEST, + /*! The highest reported bitrate is forwarded to the sender */ + AST_BRIDGE_VIDEO_SFU_REMB_HIGHEST, +}; + +/*! \brief This is used for selective forwarding unit configuration */ +struct ast_bridge_video_sfu_data { + /*! The interval at which a REMB report is generated and sent */ + unsigned int remb_send_interval; + /*! How the combined REMB report is generated */ + enum ast_bridge_video_sfu_remb_behavior remb_behavior; +}; + /*! \brief Data structure that defines a video source mode */ struct ast_bridge_video_mode { enum ast_bridge_video_mode_type mode; @@ -133,7 +151,9 @@ struct ast_bridge_video_mode { union { struct ast_bridge_video_single_src_data single_src_data; struct ast_bridge_video_talker_src_data talker_src_data; + struct ast_bridge_video_sfu_data sfu_data; } mode_data; + /*! The minimum interval between video updates */ unsigned int video_update_discard; }; @@ -912,6 +932,26 @@ void ast_bridge_set_sfu_video_mode(struct ast_bridge *bridge); void ast_bridge_set_video_update_discard(struct ast_bridge *bridge, unsigned int video_update_discard); /*! + * \brief Set the interval at which a combined REMB frame will be sent to video sources + * + * \param bridge Bridge to set the REMB send interval on + * \param remb_send_interval The REMB send interval + * + * \note This can only be called when the bridge has been set to the SFU video mode. + */ +void ast_bridge_set_remb_send_interval(struct ast_bridge *bridge, unsigned int remb_send_interval); + +/*! + * \brief Set the REMB report generation behavior on a bridge + * + * \param bridge Bridge to set the REMB behavior on + * \param behavior How REMB reports are generated + * + * \note This can only be called when the bridge has been set to the SFU video mode. + */ +void ast_brige_set_remb_behavior(struct ast_bridge *bridge, enum ast_bridge_video_sfu_remb_behavior behavior); + +/*! * \brief Update information about talker energy for talker src video mode. */ void ast_bridge_update_talker_src_video_mode(struct ast_bridge *bridge, struct ast_channel *chan, int talker_energy, int is_keyfame); @@ -945,6 +985,17 @@ void ast_bridge_remove_video_src(struct ast_bridge *bridge, struct ast_channel * */ const char *ast_bridge_video_mode_to_string(enum ast_bridge_video_mode_type video_mode); +/*! + * \brief Acquire the channel's bridge for transfer purposes. + * \since 13.21.0 + * + * \param chan Channel involved in a transfer. + * + * \return The bridge the channel is in or NULL if it either isn't + * in a bridge or should not be considered to be in a bridge. + */ +struct ast_bridge *ast_bridge_transfer_acquire_bridge(struct ast_channel *chan); + enum ast_transfer_result { /*! The transfer completed successfully */ AST_BRIDGE_TRANSFER_SUCCESS, diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h index 26439986b..d2ae39baf 100644 --- a/include/asterisk/res_pjsip.h +++ b/include/asterisk/res_pjsip.h @@ -258,6 +258,14 @@ struct ast_sip_contact { AST_STRING_FIELD(user_agent); /*! The name of the aor this contact belongs to */ AST_STRING_FIELD(aor); + /*! Asterisk Server name */ + AST_STRING_FIELD(reg_server); + /*! IP-address of the Via header in REGISTER request */ + AST_STRING_FIELD(via_addr); + /*! Content of the Call-ID header in REGISTER request */ + AST_STRING_FIELD(call_id); + /*! The name of the endpoint that added the contact */ + AST_STRING_FIELD(endpoint_name); ); /*! Absolute time that this contact is no longer valid after */ struct timeval expiration_time; @@ -269,16 +277,8 @@ struct ast_sip_contact { double qualify_timeout; /*! Endpoint that added the contact, only available in observers */ struct ast_sip_endpoint *endpoint; - /*! Asterisk Server name */ - AST_STRING_FIELD_EXTENDED(reg_server); - /*! IP-address of the Via header in REGISTER request */ - AST_STRING_FIELD_EXTENDED(via_addr); - /* Port of the Via header in REGISTER request */ + /*! Port of the Via header in REGISTER request */ int via_port; - /*! Content of the Call-ID header in REGISTER request */ - AST_STRING_FIELD_EXTENDED(call_id); - /*! The name of the endpoint that added the contact */ - AST_STRING_FIELD_EXTENDED(endpoint_name); /*! If true delete the contact on Asterisk restart/boot */ int prune_on_boot; }; @@ -751,6 +751,8 @@ struct ast_sip_endpoint { AST_STRING_FIELD(message_context); /*! Accountcode to auto-set on channels */ AST_STRING_FIELD(accountcode); + /*! If set, we'll push incoming MWI NOTIFYs to stasis using this mailbox */ + AST_STRING_FIELD(incoming_mwi_mailbox); ); /*! Configuration for extensions */ struct ast_sip_endpoint_extensions extensions; @@ -812,8 +814,6 @@ struct ast_sip_endpoint { unsigned int refer_blind_progress; /*! Whether to notifies dialog-info 'early' on INUSE && RINGING state */ unsigned int notify_early_inuse_ringing; - /*! If set, we'll push incoming MWI NOTIFYs to stasis using this mailbox */ - AST_STRING_FIELD_EXTENDED(incoming_mwi_mailbox); }; /*! URI parameter for symmetric transport */ @@ -1407,7 +1407,7 @@ struct ast_sip_endpoint *ast_sip_get_artificial_endpoint(void); * the next item on the SIP socket(s) can be serviced. On incoming messages, * Asterisk automatically will push the request to a servant thread. When your * module callback is called, processing will already be in a servant. However, - * for other PSJIP events, such as transaction state changes due to timer + * for other PJSIP events, such as transaction state changes due to timer * expirations, your module will be called into from a PJSIP thread. If you * are called into from a PJSIP thread, then you should push whatever processing * is needed to a servant as soon as possible. You can discern if you are currently @@ -1543,28 +1543,92 @@ struct ast_sip_endpoint *ast_sip_dialog_get_endpoint(pjsip_dialog *dlg); int ast_sip_push_task(struct ast_taskprocessor *serializer, int (*sip_task)(void *), void *task_data); /*! - * \brief Push a task to SIP servants and wait for it to complete + * \brief Push a task to SIP servants and wait for it to complete. * - * Like \ref ast_sip_push_task except that it blocks until the task completes. + * Like \ref ast_sip_push_task except that it blocks until the task + * completes. If the current thread is a SIP servant thread then the + * task executes immediately. Otherwise, the specified serializer + * executes the task and the current thread waits for it to complete. * - * \warning \b Never use this function in a SIP servant thread. This can potentially - * cause a deadlock. If you are in a SIP servant thread, just call your function - * in-line. + * \note PJPROJECT callbacks tend to have locks already held when + * called. * - * \warning \b Never hold locks that may be acquired by a SIP servant thread when - * calling this function. Doing so may cause a deadlock if all SIP servant threads - * are blocked waiting to acquire the lock while the thread holding the lock is - * waiting for a free SIP servant thread. + * \warning \b Never hold locks that may be acquired by a SIP servant + * thread when calling this function. Doing so may cause a deadlock + * if all SIP servant threads are blocked waiting to acquire the lock + * while the thread holding the lock is waiting for a free SIP servant + * thread. * - * \param serializer The SIP serializer to which the task belongs. May be NULL. + * \warning \b Use of this function in an ao2 destructor callback is a + * bad idea. You don't have control over which thread executes the + * destructor. Attempting to shift execution to another thread with + * this function is likely to cause deadlock. + * + * \param serializer The SIP serializer to execute the task if the + * current thread is not a SIP servant. NULL if any of the default + * serializers can be used. * \param sip_task The task to execute * \param task_data The parameter to pass to the task when it executes - * \retval 0 Success - * \retval -1 Failure + * + * \note The sip_task() return value may need to be distinguished from + * the failure to push the task. + * + * \return sip_task() return value on success. + * \retval -1 Failure to push the task. + */ +int ast_sip_push_task_wait_servant(struct ast_taskprocessor *serializer, int (*sip_task)(void *), void *task_data); + +/*! + * \brief Push a task to SIP servants and wait for it to complete. + * \deprecated Replaced with ast_sip_push_task_wait_servant(). */ int ast_sip_push_task_synchronous(struct ast_taskprocessor *serializer, int (*sip_task)(void *), void *task_data); /*! + * \brief Push a task to the serializer and wait for it to complete. + * + * Like \ref ast_sip_push_task except that it blocks until the task is + * completed by the specified serializer. If the specified serializer + * is the current thread then the task executes immediately. + * + * \note PJPROJECT callbacks tend to have locks already held when + * called. + * + * \warning \b Never hold locks that may be acquired by a SIP servant + * thread when calling this function. Doing so may cause a deadlock + * if all SIP servant threads are blocked waiting to acquire the lock + * while the thread holding the lock is waiting for a free SIP servant + * thread for the serializer to execute in. + * + * \warning \b Never hold locks that may be acquired by the serializer + * when calling this function. Doing so will cause a deadlock. + * + * \warning \b Never use this function in the pjsip monitor thread (It + * is a SIP servant thread). This is likely to cause a deadlock. + * + * \warning \b Use of this function in an ao2 destructor callback is a + * bad idea. You don't have control over which thread executes the + * destructor. Attempting to shift execution to another thread with + * this function is likely to cause deadlock. + * + * \param serializer The SIP serializer to execute the task. NULL if + * any of the default serializers can be used. + * \param sip_task The task to execute + * \param task_data The parameter to pass to the task when it executes + * + * \note It is generally better to call + * ast_sip_push_task_wait_servant() if you pass NULL for the + * serializer parameter. + * + * \note The sip_task() return value may need to be distinguished from + * the failure to push the task. + * + * \return sip_task() return value on success. + * \retval -1 Failure to push the task. + */ +int ast_sip_push_task_wait_serializer(struct ast_taskprocessor *serializer, int (*sip_task)(void *), void *task_data); + +/*! * \brief Determine if the current thread is a SIP servant thread * * \retval 0 This is not a SIP servant thread @@ -1588,13 +1652,13 @@ enum ast_sip_scheduler_task_flags { /*! * Run at a fixed interval. - * Stop scheduling if the callback returns 0. + * Stop scheduling if the callback returns <= 0. * Any other value is ignored. */ AST_SIP_SCHED_TASK_FIXED = (0 << 0), /*! * Run at a variable interval. - * Stop scheduling if the callback returns 0. + * Stop scheduling if the callback returns <= 0. * Any other return value is used as the new interval. */ AST_SIP_SCHED_TASK_VARIABLE = (1 << 0), @@ -1620,16 +1684,23 @@ enum ast_sip_scheduler_task_flags { */ AST_SIP_SCHED_TASK_DATA_FREE = ( 1 << 3 ), - /*! \brief AST_SIP_SCHED_TASK_PERIODIC - * The task is scheduled at multiples of interval + /*! + * \brief The task is scheduled at multiples of interval * \see Interval */ AST_SIP_SCHED_TASK_PERIODIC = (0 << 4), - /*! \brief AST_SIP_SCHED_TASK_DELAY - * The next invocation of the task is at last finish + interval + /*! + * \brief The next invocation of the task is at last finish + interval * \see Interval */ AST_SIP_SCHED_TASK_DELAY = (1 << 4), + /*! + * \brief The scheduled task's events are tracked in the debug log. + * \details + * Schedule events such as scheduling, running, rescheduling, canceling, + * and destroying are logged about the task. + */ + AST_SIP_SCHED_TASK_TRACK = (1 << 5), }; /*! @@ -1673,7 +1744,7 @@ struct ast_sip_sched_task; * */ struct ast_sip_sched_task *ast_sip_schedule_task(struct ast_taskprocessor *serializer, - int interval, ast_sip_task sip_task, char *name, void *task_data, + int interval, ast_sip_task sip_task, const char *name, void *task_data, enum ast_sip_scheduler_task_flags flags); /*! diff --git a/include/asterisk/rtp_engine.h b/include/asterisk/rtp_engine.h index b552948d2..3426b2a1e 100644 --- a/include/asterisk/rtp_engine.h +++ b/include/asterisk/rtp_engine.h @@ -292,10 +292,16 @@ struct ast_rtp_payload_type { #define AST_RTP_RTCP_SR 200 /*! Receiver Report */ #define AST_RTP_RTCP_RR 201 +/*! Transport Layer Feed Back (From RFC4585 also RFC5104) */ +#define AST_RTP_RTCP_RTPFB 205 /*! Payload Specific Feed Back (From RFC4585 also RFC5104) */ -#define AST_RTP_RTCP_PSFB 206 +#define AST_RTP_RTCP_PSFB 206 /* Common RTCP feedback message types */ +/*! Generic NACK (From RFC4585 also RFC5104) */ +#define AST_RTP_RTCP_FMT_NACK 1 +/*! Picture loss indication (From RFC4585) */ +#define AST_RTP_RTCP_FMT_PLI 1 /*! Full INTRA-frame Request (From RFC5104) */ #define AST_RTP_RTCP_FMT_FIR 4 /*! REMB Information (From draft-alvestrand-rmcat-remb-03) */ diff --git a/include/asterisk/stasis_bridges.h b/include/asterisk/stasis_bridges.h index 05d356cc2..a455a5b02 100644 --- a/include/asterisk/stasis_bridges.h +++ b/include/asterisk/stasis_bridges.h @@ -46,6 +46,8 @@ struct ast_bridge_snapshot { AST_STRING_FIELD(creator); /*! Name given to the bridge by its creator */ AST_STRING_FIELD(name); + /*! Unique ID of the channel providing video, if one exists */ + AST_STRING_FIELD(video_source_id); ); /*! AO2 container of bare channel uniqueid strings participating in the bridge. * Allocated from ast_str_container_alloc() */ @@ -60,8 +62,6 @@ struct ast_bridge_snapshot { unsigned int num_active; /*! The video mode of the bridge */ enum ast_bridge_video_mode_type video_mode; - /*! Unique ID of the channel providing video, if one exists */ - AST_STRING_FIELD_EXTENDED(video_source_id); }; /*! diff --git a/include/asterisk/utils.h b/include/asterisk/utils.h index 4da7fa465..b892cda9e 100644 --- a/include/asterisk/utils.h +++ b/include/asterisk/utils.h @@ -578,6 +578,13 @@ void DO_CRASH_NORETURN __ast_assert_failed(int condition, const char *condition_ #ifdef AST_DEVMODE #define ast_assert(a) _ast_assert(a, # a, __FILE__, __LINE__, __PRETTY_FUNCTION__) +#define ast_assert_return(a, ...) \ +({ \ + if (__builtin_expect(!(a), 1)) { \ + _ast_assert(0, # a, __FILE__, __LINE__, __PRETTY_FUNCTION__); \ + return __VA_ARGS__; \ + }\ +}) static void force_inline _ast_assert(int condition, const char *condition_str, const char *file, int line, const char *function) { if (__builtin_expect(!condition, 1)) { @@ -586,6 +593,12 @@ static void force_inline _ast_assert(int condition, const char *condition_str, c } #else #define ast_assert(a) +#define ast_assert_return(a, ...) \ +({ \ + if (__builtin_expect(!(a), 1)) { \ + return __VA_ARGS__; \ + }\ +}) #endif /*! diff --git a/main/bridge.c b/main/bridge.c index 1109c4b76..2b347fd3f 100644 --- a/main/bridge.c +++ b/main/bridge.c @@ -3850,6 +3850,24 @@ void ast_bridge_set_video_update_discard(struct ast_bridge *bridge, unsigned int ast_bridge_unlock(bridge); } +void ast_bridge_set_remb_send_interval(struct ast_bridge *bridge, unsigned int remb_send_interval) +{ + ast_assert(bridge->softmix.video_mode.mode == AST_BRIDGE_VIDEO_MODE_SFU); + + ast_bridge_lock(bridge); + bridge->softmix.video_mode.mode_data.sfu_data.remb_send_interval = remb_send_interval; + ast_bridge_unlock(bridge); +} + +void ast_brige_set_remb_behavior(struct ast_bridge *bridge, enum ast_bridge_video_sfu_remb_behavior behavior) +{ + ast_assert(bridge->softmix.video_mode.mode == AST_BRIDGE_VIDEO_MODE_SFU); + + ast_bridge_lock(bridge); + bridge->softmix.video_mode.mode_data.sfu_data.remb_behavior = behavior; + ast_bridge_unlock(bridge); +} + void ast_bridge_update_talker_src_video_mode(struct ast_bridge *bridge, struct ast_channel *chan, int talker_energy, int is_keyframe) { struct ast_bridge_video_talker_src_data *data; @@ -4420,7 +4438,7 @@ static void set_transfer_variables_all(struct ast_channel *transferer, struct ao ao2_iterator_destroy(&iter); } -static struct ast_bridge *acquire_bridge(struct ast_channel *chan) +struct ast_bridge *ast_bridge_transfer_acquire_bridge(struct ast_channel *chan) { struct ast_bridge *bridge; @@ -4461,7 +4479,7 @@ enum ast_transfer_result ast_bridge_transfer_blind(int is_external, return AST_BRIDGE_TRANSFER_FAIL; } - bridge = acquire_bridge(transferer); + bridge = ast_bridge_transfer_acquire_bridge(transferer); if (!bridge) { transfer_result = AST_BRIDGE_TRANSFER_INVALID; goto publish; @@ -4708,8 +4726,8 @@ enum ast_transfer_result ast_bridge_transfer_attended(struct ast_channel *to_tra const char *app = NULL; int hangup_target = 0; - to_transferee_bridge = acquire_bridge(to_transferee); - to_target_bridge = acquire_bridge(to_transfer_target); + to_transferee_bridge = ast_bridge_transfer_acquire_bridge(to_transferee); + to_target_bridge = ast_bridge_transfer_acquire_bridge(to_transfer_target); transfer_msg = ast_attended_transfer_message_create(1, to_transferee, to_transferee_bridge, to_transfer_target, to_target_bridge, NULL, NULL); diff --git a/main/stasis_bridges.c b/main/stasis_bridges.c index 4b68559de..59b9685ef 100644 --- a/main/stasis_bridges.c +++ b/main/stasis_bridges.c @@ -246,8 +246,7 @@ struct ast_bridge_snapshot *ast_bridge_snapshot_create(struct ast_bridge *bridge return NULL; } - if (ast_string_field_init(snapshot, 128) - || ast_string_field_init_extended(snapshot, video_source_id)) { + if (ast_string_field_init(snapshot, 128)) { ao2_ref(snapshot, -1); return NULL; diff --git a/menuselect/configure b/menuselect/configure index 6e5331edd..a0aa10928 100755 --- a/menuselect/configure +++ b/menuselect/configure @@ -4392,7 +4392,7 @@ fi if test ! "x${CONFIG_LIBXML2}" = xNo; then LIBXML2_INCLUDE=$(${CONFIG_LIBXML2} --cflags) - LIBXML2_INCLUDE=$(echo ${LIBXML2_INCLUDE} | $SED -e "s|-I|-I${LIBXML2_DIR}|g") + LIBXML2_INCLUDE=$(echo ${LIBXML2_INCLUDE} | $SED -e "s|-I|-I${LIBXML2_DIR}|g" -e "s|-std=c99||g") LIBXML2_LIB=$(${CONFIG_LIBXML2} --libs) LIBXML2_LIB=$(echo ${LIBXML2_LIB} | $SED -e "s|-L|-L${LIBXML2_DIR}|g") @@ -4633,7 +4633,7 @@ else $as_echo "yes" >&6; } PBX_GTK2=1 - GTK2_INCLUDE="$GTK2_CFLAGS" + GTK2_INCLUDE=$(echo ${GTK2_CFLAGS} | $SED -e "s|-std=c99||g") GTK2_LIB="$GTK2_LIBS" $as_echo "#define HAVE_GTK2 1" >>confdefs.h diff --git a/res/res_musiconhold.c b/res/res_musiconhold.c index 55b14c934..1c8728cf7 100644 --- a/res/res_musiconhold.c +++ b/res/res_musiconhold.c @@ -333,7 +333,6 @@ static int ast_moh_files_next(struct ast_channel *chan) } } else { state->announcement = 0; - state->samples = 0; } if (!state->class->total_files) { diff --git a/res/res_pjsip.c b/res/res_pjsip.c index 7c9929740..19e6e1d13 100644 --- a/res/res_pjsip.c +++ b/res/res_pjsip.c @@ -2743,7 +2743,7 @@ static int register_service(void *data) int ast_sip_register_service(pjsip_module *module) { - return ast_sip_push_task_synchronous(NULL, register_service, &module); + return ast_sip_push_task_wait_servant(NULL, register_service, &module); } static int unregister_service(void *data) @@ -2759,7 +2759,7 @@ static int unregister_service(void *data) void ast_sip_unregister_service(pjsip_module *module) { - ast_sip_push_task_synchronous(NULL, unregister_service, &module); + ast_sip_push_task_wait_servant(NULL, unregister_service, &module); } static struct ast_sip_authenticator *registered_authenticator; @@ -3009,7 +3009,7 @@ static char *cli_dump_endpt(struct ast_cli_entry *e, int cmd, struct ast_cli_arg return CLI_SHOWUSAGE; } - ast_sip_push_task_synchronous(NULL, do_cli_dump_endpt, a); + ast_sip_push_task_wait_servant(NULL, do_cli_dump_endpt, a); return CLI_SUCCESS; } @@ -3801,8 +3801,6 @@ int ast_sip_create_request(const char *method, struct pjsip_dialog *dlg, { const pjsip_method *pmethod = get_pjsip_method(method); - ast_assert(endpoint != NULL); - if (!pmethod) { ast_log(LOG_WARNING, "Unknown method '%s'. Cannot send request\n", method); return -1; @@ -3811,6 +3809,7 @@ int ast_sip_create_request(const char *method, struct pjsip_dialog *dlg, if (dlg) { return create_in_dialog_request(pmethod, dlg, tdata); } else { + ast_assert(endpoint != NULL); return create_out_of_dialog_request(pmethod, endpoint, uri, contact, tdata); } } @@ -4485,21 +4484,30 @@ static int serializer_pool_setup(void) return 0; } +static struct ast_taskprocessor *serializer_pool_pick(void) +{ + struct ast_taskprocessor *serializer; + + unsigned int pos; + + /* + * Pick a serializer to use from the pool. + * + * Note: We don't care about any reentrancy behavior + * when incrementing serializer_pool_pos. If it gets + * incorrectly incremented it doesn't matter. + */ + pos = serializer_pool_pos++; + pos %= SERIALIZER_POOL_SIZE; + serializer = serializer_pool[pos]; + + return serializer; +} + int ast_sip_push_task(struct ast_taskprocessor *serializer, int (*sip_task)(void *), void *task_data) { if (!serializer) { - unsigned int pos; - - /* - * Pick a serializer to use from the pool. - * - * Note: We don't care about any reentrancy behavior - * when incrementing serializer_pool_pos. If it gets - * incorrectly incremented it doesn't matter. - */ - pos = serializer_pool_pos++; - pos %= SERIALIZER_POOL_SIZE; - serializer = serializer_pool[pos]; + serializer = serializer_pool_pick(); } return ast_taskprocessor_push(serializer, sip_task, task_data); @@ -4523,9 +4531,8 @@ static int sync_task(void *data) /* * Once we unlock std->lock after signaling, we cannot access - * std again. The thread waiting within - * ast_sip_push_task_synchronous() is free to continue and - * release its local variable (std). + * std again. The thread waiting within ast_sip_push_task_wait() + * is free to continue and release its local variable (std). */ ast_mutex_lock(&std->lock); std->complete = 1; @@ -4535,15 +4542,11 @@ static int sync_task(void *data) return ret; } -int ast_sip_push_task_synchronous(struct ast_taskprocessor *serializer, int (*sip_task)(void *), void *task_data) +static int ast_sip_push_task_wait(struct ast_taskprocessor *serializer, int (*sip_task)(void *), void *task_data) { /* This method is an onion */ struct sync_task_data std; - if (ast_sip_thread_is_servant()) { - return sip_task(task_data); - } - memset(&std, 0, sizeof(std)); ast_mutex_init(&std.lock); ast_cond_init(&std.cond, NULL); @@ -4567,6 +4570,42 @@ int ast_sip_push_task_synchronous(struct ast_taskprocessor *serializer, int (*si return std.fail; } +int ast_sip_push_task_wait_servant(struct ast_taskprocessor *serializer, int (*sip_task)(void *), void *task_data) +{ + if (ast_sip_thread_is_servant()) { + return sip_task(task_data); + } + + return ast_sip_push_task_wait(serializer, sip_task, task_data); +} + +int ast_sip_push_task_synchronous(struct ast_taskprocessor *serializer, int (*sip_task)(void *), void *task_data) +{ + return ast_sip_push_task_wait_servant(serializer, sip_task, task_data); +} + +int ast_sip_push_task_wait_serializer(struct ast_taskprocessor *serializer, int (*sip_task)(void *), void *task_data) +{ + if (!serializer) { + /* Caller doesn't care which PJSIP serializer the task executes under. */ + serializer = serializer_pool_pick(); + if (!serializer) { + /* No serializer picked to execute the task */ + return -1; + } + } + if (ast_taskprocessor_is_task(serializer)) { + /* + * We are the requested serializer so we must execute + * the task now or deadlock waiting on ourself to + * execute it. + */ + return sip_task(task_data); + } + + return ast_sip_push_task_wait(serializer, sip_task, task_data); +} + void ast_copy_pj_str(char *dest, const pj_str_t *src, size_t size) { size_t chars_to_copy = MIN(size - 1, pj_strlen(src)); @@ -5192,7 +5231,7 @@ static int reload_module(void) * We must wait for the reload to complete so multiple * reloads cannot happen at the same time. */ - if (ast_sip_push_task_synchronous(NULL, reload_configuration_task, NULL)) { + if (ast_sip_push_task_wait_servant(NULL, reload_configuration_task, NULL)) { ast_log(LOG_WARNING, "Failed to reload PJSIP\n"); return -1; } @@ -5209,7 +5248,7 @@ static int unload_module(void) /* The thread this is called from cannot call PJSIP/PJLIB functions, * so we have to push the work to the threadpool to handle */ - ast_sip_push_task_synchronous(NULL, unload_pjsip, NULL); + ast_sip_push_task_wait_servant(NULL, unload_pjsip, NULL); ast_sip_destroy_scheduler(); serializer_pool_shutdown(); ast_threadpool_shutdown(sip_threadpool); diff --git a/res/res_pjsip/config_system.c b/res/res_pjsip/config_system.c index dfd92404b..ed2b5d232 100644 --- a/res/res_pjsip/config_system.c +++ b/res/res_pjsip/config_system.c @@ -282,5 +282,5 @@ static int system_create_resolver_and_set_nameservers(void *data) void ast_sip_initialize_dns(void) { - ast_sip_push_task_synchronous(NULL, system_create_resolver_and_set_nameservers, NULL); + ast_sip_push_task_wait_servant(NULL, system_create_resolver_and_set_nameservers, NULL); } diff --git a/res/res_pjsip/config_transport.c b/res/res_pjsip/config_transport.c index 15c03769b..dd7c7049d 100644 --- a/res/res_pjsip/config_transport.c +++ b/res/res_pjsip/config_transport.c @@ -267,7 +267,7 @@ static void sip_transport_state_destroy(void *obj) { struct ast_sip_transport_state *state = obj; - ast_sip_push_task_synchronous(NULL, destroy_sip_transport_state, state); + ast_sip_push_task_wait_servant(NULL, destroy_sip_transport_state, state); } /*! \brief Destructor for ast_sip_transport state information */ diff --git a/res/res_pjsip/location.c b/res/res_pjsip/location.c index 22da80577..6e79dc40b 100644 --- a/res/res_pjsip/location.c +++ b/res/res_pjsip/location.c @@ -133,11 +133,6 @@ static void *contact_alloc(const char *name) return NULL; } - ast_string_field_init_extended(contact, endpoint_name); - ast_string_field_init_extended(contact, reg_server); - ast_string_field_init_extended(contact, via_addr); - ast_string_field_init_extended(contact, call_id); - /* Dynamic contacts are delimited with ";@" and static ones with "@@" */ if ((aor_separator = strstr(id, ";@")) || (aor_separator = strstr(id, "@@"))) { *aor_separator = '\0'; diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c index 3094f248e..fb84a1f60 100644 --- a/res/res_pjsip/pjsip_configuration.c +++ b/res/res_pjsip/pjsip_configuration.c @@ -2248,8 +2248,6 @@ void *ast_sip_endpoint_alloc(const char *name) return NULL; } - ast_string_field_init_extended(endpoint, incoming_mwi_mailbox); - if (!(endpoint->media.codecs = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) { ao2_cleanup(endpoint); return NULL; diff --git a/res/res_pjsip/pjsip_scheduler.c b/res/res_pjsip/pjsip_scheduler.c index e4459da66..bbf666fd7 100644 --- a/res/res_pjsip/pjsip_scheduler.c +++ b/res/res_pjsip/pjsip_scheduler.c @@ -28,6 +28,7 @@ #include "asterisk/res_pjsip.h" #include "include/res_pjsip_private.h" #include "asterisk/res_pjsip_cli.h" +#include "asterisk/taskprocessor.h" #define TASK_BUCKETS 53 @@ -36,31 +37,31 @@ static struct ao2_container *tasks; static int task_count; struct ast_sip_sched_task { - /*! ast_sip_sched task id */ - uint32_t task_id; - /*! ast_sched scheudler id */ - int current_scheduler_id; - /*! task is currently running */ - int is_running; - /*! task */ - ast_sip_task task; + /*! The serializer to be used (if any) (Holds a ref) */ + struct ast_taskprocessor *serializer; /*! task data */ void *task_data; - /*! reschedule interval in milliseconds */ - int interval; - /*! the time the task was queued */ + /*! task function */ + ast_sip_task task; + /*! the time the task was originally scheduled/queued */ struct timeval when_queued; /*! the last time the task was started */ struct timeval last_start; /*! the last time the task was ended */ struct timeval last_end; + /*! When the periodic task is next expected to run */ + struct timeval next_periodic; + /*! reschedule interval in milliseconds */ + int interval; + /*! ast_sched scheudler id */ + int current_scheduler_id; + /*! task is currently running */ + int is_running; /*! times run */ int run_count; /*! the task reschedule, cleanup and policy flags */ enum ast_sip_scheduler_task_flags flags; - /*! the serializer to be used (if any) */ - struct ast_taskprocessor *serializer; - /* A name to be associated with the task */ + /*! A name to be associated with the task */ char name[0]; }; @@ -76,14 +77,22 @@ static int push_to_serializer(const void *data); */ static int run_task(void *data) { - RAII_VAR(struct ast_sip_sched_task *, schtd, ao2_bump(data), ao2_cleanup); + RAII_VAR(struct ast_sip_sched_task *, schtd, data, ao2_cleanup); int res; int delay; + if (!schtd->interval) { + /* Task was cancelled while waiting to be executed by the serializer */ + return -1; + } + + if (schtd->flags & AST_SIP_SCHED_TASK_TRACK) { + ast_log(LOG_DEBUG, "Sched %p: Running %s\n", schtd, schtd->name); + } ao2_lock(schtd); schtd->last_start = ast_tvnow(); schtd->is_running = 1; - schtd->run_count++; + ++schtd->run_count; ao2_unlock(schtd); res = schtd->task(schtd->task_data); @@ -93,10 +102,10 @@ static int run_task(void *data) schtd->last_end = ast_tvnow(); /* - * Don't restart if the task returned 0 or if the interval + * Don't restart if the task returned <= 0 or if the interval * was set to 0 while the task was running */ - if (!res || !schtd->interval) { + if (res <= 0 || !schtd->interval) { schtd->interval = 0; ao2_unlock(schtd); ao2_unlink(tasks, schtd); @@ -110,18 +119,31 @@ static int run_task(void *data) if (schtd->flags & AST_SIP_SCHED_TASK_DELAY) { delay = schtd->interval; } else { - delay = schtd->interval - (ast_tvdiff_ms(schtd->last_end, schtd->last_start) % schtd->interval); + int64_t diff; + + /* Determine next periodic interval we need to expire. */ + do { + schtd->next_periodic = ast_tvadd(schtd->next_periodic, + ast_samp2tv(schtd->interval, 1000)); + diff = ast_tvdiff_ms(schtd->next_periodic, schtd->last_end); + } while (diff <= 0); + delay = diff; } - schtd->current_scheduler_id = ast_sched_add(scheduler_context, delay, push_to_serializer, (const void *)schtd); + schtd->current_scheduler_id = ast_sched_add(scheduler_context, delay, push_to_serializer, schtd); if (schtd->current_scheduler_id < 0) { schtd->interval = 0; ao2_unlock(schtd); + ast_log(LOG_ERROR, "Sched %p: Failed to reschedule task %s\n", schtd, schtd->name); ao2_unlink(tasks, schtd); return -1; } ao2_unlock(schtd); + if (schtd->flags & AST_SIP_SCHED_TASK_TRACK) { + ast_log(LOG_DEBUG, "Sched %p: Rescheduled %s for %d ms\n", schtd, schtd->name, + delay); + } return 0; } @@ -133,9 +155,32 @@ static int run_task(void *data) static int push_to_serializer(const void *data) { struct ast_sip_sched_task *schtd = (struct ast_sip_sched_task *)data; + int sched_id; + ao2_lock(schtd); + sched_id = schtd->current_scheduler_id; + schtd->current_scheduler_id = -1; + ao2_unlock(schtd); + if (sched_id < 0) { + /* Task was cancelled while waiting on the lock */ + return 0; + } + + if (schtd->flags & AST_SIP_SCHED_TASK_TRACK) { + ast_log(LOG_DEBUG, "Sched %p: Ready to run %s\n", schtd, schtd->name); + } + ao2_t_ref(schtd, +1, "Give ref to run_task()"); if (ast_sip_push_task(schtd->serializer, run_task, schtd)) { - ao2_ref(schtd, -1); + /* + * Oh my. Have to cancel the scheduled item because we + * unexpectedly cannot run it anymore. + */ + ao2_unlink(tasks, schtd); + ao2_lock(schtd); + schtd->interval = 0; + ao2_unlock(schtd); + + ao2_t_ref(schtd, -1, "Failed so release run_task() ref"); } return 0; @@ -144,48 +189,54 @@ static int push_to_serializer(const void *data) int ast_sip_sched_task_cancel(struct ast_sip_sched_task *schtd) { int res; + int sched_id; - if (!ao2_ref_and_lock(schtd)) { - return -1; - } - - if (schtd->current_scheduler_id < 0 || schtd->interval <= 0) { - ao2_unlock_and_unref(schtd); - return 0; + if (schtd->flags & AST_SIP_SCHED_TASK_TRACK) { + ast_log(LOG_DEBUG, "Sched %p: Canceling %s\n", schtd, schtd->name); } + /* + * Prevent any tasks in the serializer queue from + * running and restarting the scheduled item on us + * first. + */ + ao2_lock(schtd); schtd->interval = 0; - ao2_unlock_and_unref(schtd); + + sched_id = schtd->current_scheduler_id; + schtd->current_scheduler_id = -1; + ao2_unlock(schtd); + res = ast_sched_del(scheduler_context, sched_id); + ao2_unlink(tasks, schtd); - res = ast_sched_del(scheduler_context, schtd->current_scheduler_id); return res; } int ast_sip_sched_task_cancel_by_name(const char *name) { - RAII_VAR(struct ast_sip_sched_task *, schtd, NULL, ao2_cleanup); + int res; + struct ast_sip_sched_task *schtd; if (ast_strlen_zero(name)) { return -1; } - schtd = ao2_find(tasks, name, OBJ_SEARCH_KEY | OBJ_NOLOCK); + schtd = ao2_find(tasks, name, OBJ_SEARCH_KEY); if (!schtd) { return -1; } - return ast_sip_sched_task_cancel(schtd); + res = ast_sip_sched_task_cancel(schtd); + ao2_ref(schtd, -1); + return res; } int ast_sip_sched_task_get_times(struct ast_sip_sched_task *schtd, struct timeval *queued, struct timeval *last_start, struct timeval *last_end) { - if (!ao2_ref_and_lock(schtd)) { - return -1; - } - + ao2_lock(schtd); if (queued) { memcpy(queued, &schtd->when_queued, sizeof(struct timeval)); } @@ -195,8 +246,7 @@ int ast_sip_sched_task_get_times(struct ast_sip_sched_task *schtd, if (last_end) { memcpy(last_end, &schtd->last_end, sizeof(struct timeval)); } - - ao2_unlock_and_unref(schtd); + ao2_unlock(schtd); return 0; } @@ -204,18 +254,21 @@ int ast_sip_sched_task_get_times(struct ast_sip_sched_task *schtd, int ast_sip_sched_task_get_times_by_name(const char *name, struct timeval *queued, struct timeval *last_start, struct timeval *last_end) { - RAII_VAR(struct ast_sip_sched_task *, schtd, NULL, ao2_cleanup); + int res; + struct ast_sip_sched_task *schtd; if (ast_strlen_zero(name)) { return -1; } - schtd = ao2_find(tasks, name, OBJ_SEARCH_KEY | OBJ_NOLOCK); + schtd = ao2_find(tasks, name, OBJ_SEARCH_KEY); if (!schtd) { return -1; } - return ast_sip_sched_task_get_times(schtd, queued, last_start, last_end); + res = ast_sip_sched_task_get_times(schtd, queued, last_start, last_end); + ao2_ref(schtd, -1); + return res; } int ast_sip_sched_task_get_name(struct ast_sip_sched_task *schtd, char *name, size_t maxlen) @@ -224,13 +277,9 @@ int ast_sip_sched_task_get_name(struct ast_sip_sched_task *schtd, char *name, si return -1; } - if (!ao2_ref_and_lock(schtd)) { - return -1; - } - + ao2_lock(schtd); ast_copy_string(name, schtd->name, maxlen); - - ao2_unlock_and_unref(schtd); + ao2_unlock(schtd); return 0; } @@ -241,9 +290,7 @@ int ast_sip_sched_task_get_next_run(struct ast_sip_sched_task *schtd) struct timeval since_when; struct timeval now; - if (!ao2_ref_and_lock(schtd)) { - return -1; - } + ao2_lock(schtd); if (schtd->interval) { delay = schtd->interval; @@ -262,103 +309,136 @@ int ast_sip_sched_task_get_next_run(struct ast_sip_sched_task *schtd) delay = -1; } - ao2_unlock_and_unref(schtd); + ao2_unlock(schtd); return delay; } int ast_sip_sched_task_get_next_run_by_name(const char *name) { - RAII_VAR(struct ast_sip_sched_task *, schtd, NULL, ao2_cleanup); + int next_run; + struct ast_sip_sched_task *schtd; if (ast_strlen_zero(name)) { return -1; } - schtd = ao2_find(tasks, name, OBJ_SEARCH_KEY | OBJ_NOLOCK); + schtd = ao2_find(tasks, name, OBJ_SEARCH_KEY); if (!schtd) { return -1; } - return ast_sip_sched_task_get_next_run(schtd); + next_run = ast_sip_sched_task_get_next_run(schtd); + ao2_ref(schtd, -1); + return next_run; } int ast_sip_sched_is_task_running(struct ast_sip_sched_task *schtd) { - if (!schtd) { - return 0; - } - - return schtd->is_running; + return schtd ? schtd->is_running : 0; } int ast_sip_sched_is_task_running_by_name(const char *name) { - RAII_VAR(struct ast_sip_sched_task *, schtd, NULL, ao2_cleanup); + int is_running; + struct ast_sip_sched_task *schtd; if (ast_strlen_zero(name)) { return 0; } - schtd = ao2_find(tasks, name, OBJ_SEARCH_KEY | OBJ_NOLOCK); + schtd = ao2_find(tasks, name, OBJ_SEARCH_KEY); if (!schtd) { return 0; } - return schtd->is_running; + is_running = schtd->is_running; + ao2_ref(schtd, -1); + return is_running; } -static void schtd_destructor(void *data) +static void schtd_dtor(void *data) { struct ast_sip_sched_task *schtd = data; + if (schtd->flags & AST_SIP_SCHED_TASK_TRACK) { + ast_log(LOG_DEBUG, "Sched %p: Destructor %s\n", schtd, schtd->name); + } if (schtd->flags & AST_SIP_SCHED_TASK_DATA_AO2) { /* release our own ref, then release the callers if asked to do so */ ao2_ref(schtd->task_data, (schtd->flags & AST_SIP_SCHED_TASK_DATA_FREE) ? -2 : -1); } else if (schtd->task_data && (schtd->flags & AST_SIP_SCHED_TASK_DATA_FREE)) { ast_free(schtd->task_data); } + ast_taskprocessor_unreference(schtd->serializer); } struct ast_sip_sched_task *ast_sip_schedule_task(struct ast_taskprocessor *serializer, - int interval, ast_sip_task sip_task, char *name, void *task_data, enum ast_sip_scheduler_task_flags flags) + int interval, ast_sip_task sip_task, const char *name, void *task_data, + enum ast_sip_scheduler_task_flags flags) { #define ID_LEN 13 /* task_deadbeef */ struct ast_sip_sched_task *schtd; int res; - if (interval < 0) { + if (interval <= 0) { return NULL; } - schtd = ao2_alloc((sizeof(*schtd) + (!ast_strlen_zero(name) ? strlen(name) : ID_LEN) + 1), schtd_destructor); + schtd = ao2_alloc((sizeof(*schtd) + (!ast_strlen_zero(name) ? strlen(name) : ID_LEN) + 1), + schtd_dtor); if (!schtd) { return NULL; } - schtd->task_id = ast_atomic_fetchadd_int(&task_count, 1); - schtd->serializer = serializer; + schtd->serializer = ao2_bump(serializer); + schtd->task_data = task_data; schtd->task = sip_task; + schtd->interval = interval; + schtd->flags = flags; if (!ast_strlen_zero(name)) { strcpy(schtd->name, name); /* Safe */ } else { - sprintf(schtd->name, "task_%08x", schtd->task_id); + uint32_t task_id; + + task_id = ast_atomic_fetchadd_int(&task_count, 1); + sprintf(schtd->name, "task_%08x", task_id); + } + if (schtd->flags & AST_SIP_SCHED_TASK_TRACK) { + ast_log(LOG_DEBUG, "Sched %p: Scheduling %s for %d ms\n", schtd, schtd->name, + interval); } - schtd->task_data = task_data; - schtd->flags = flags; - schtd->interval = interval; schtd->when_queued = ast_tvnow(); + if (!(schtd->flags & AST_SIP_SCHED_TASK_DELAY)) { + schtd->next_periodic = ast_tvadd(schtd->when_queued, + ast_samp2tv(schtd->interval, 1000)); + } if (flags & AST_SIP_SCHED_TASK_DATA_AO2) { ao2_ref(task_data, +1); } - res = ast_sched_add(scheduler_context, interval, push_to_serializer, (const void *)schtd); + + /* + * We must put it in the 'tasks' container before scheduling + * the task because we don't want the push_to_serializer() + * sched task to "remove" it on failure before we even put + * it in. If this happens then nothing would remove it from + * the 'tasks' container. + */ + ao2_link(tasks, schtd); + + /* + * Lock so we are guaranteed to get the sched id set before + * the push_to_serializer() sched task can clear it. + */ + ao2_lock(schtd); + res = ast_sched_add(scheduler_context, interval, push_to_serializer, schtd); + schtd->current_scheduler_id = res; + ao2_unlock(schtd); if (res < 0) { + ao2_unlink(tasks, schtd); ao2_ref(schtd, -1); return NULL; - } else { - schtd->current_scheduler_id = res; - ao2_link(tasks, schtd); } return schtd; @@ -367,16 +447,17 @@ struct ast_sip_sched_task *ast_sip_schedule_task(struct ast_taskprocessor *seria static char *cli_show_tasks(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { - struct ao2_iterator i; + struct ao2_iterator iter; + struct ao2_container *sorted_tasks; struct ast_sip_sched_task *schtd; - const char *log_format = ast_logger_get_dateformat(); + const char *log_format; struct ast_tm tm; char queued[32]; char last_start[32]; char next_start[32]; int datelen; - struct timeval now = ast_tvnow(); - const char *separator = "======================================"; + struct timeval now; + static const char separator[] = "============================================="; switch (cmd) { case CLI_INIT: @@ -392,26 +473,47 @@ static char *cli_show_tasks(struct ast_cli_entry *e, int cmd, struct ast_cli_arg return CLI_SHOWUSAGE; } + /* Get a sorted snapshot of the scheduled tasks */ + sorted_tasks = ao2_container_alloc_rbtree(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, + ast_sip_sched_task_sort_fn, NULL); + if (!sorted_tasks) { + return CLI_SUCCESS; + } + if (ao2_container_dup(sorted_tasks, tasks, 0)) { + ao2_ref(sorted_tasks, -1); + return CLI_SUCCESS; + } + + now = ast_tvnow(); + log_format = ast_logger_get_dateformat(); + ast_localtime(&now, &tm, NULL); datelen = ast_strftime(queued, sizeof(queued), log_format, &tm); ast_cli(a->fd, "PJSIP Scheduled Tasks:\n\n"); - ast_cli(a->fd, " %1$-24s %2$-9s %3$-9s %4$-5s %6$-*5$s %7$-*5$s %8$-*5$s %9$7s\n", + ast_cli(a->fd, "%1$-45s %2$-9s %3$-9s %4$-5s %6$-*5$s %7$-*5$s %8$-*5$s %9$7s\n", "Task Name", "Interval", "Times Run", "State", datelen, "Queued", "Last Started", "Next Start", "( secs)"); - ast_cli(a->fd, " %1$-24.24s %2$-9.9s %3$-9.9s %4$-5.5s %6$-*5$.*5$s %7$-*5$.*5$s %9$-*8$.*8$s\n", + ast_cli(a->fd, "%1$-45.45s %2$-9.9s %3$-9.9s %4$-5.5s %6$-*5$.*5$s %7$-*5$.*5$s %9$-*8$.*8$s\n", separator, separator, separator, separator, datelen, separator, separator, datelen + 8, separator); + iter = ao2_iterator_init(sorted_tasks, AO2_ITERATOR_UNLINK); + for (; (schtd = ao2_iterator_next(&iter)); ao2_ref(schtd, -1)) { + int next_run_sec; + struct timeval next; + + ao2_lock(schtd); - ao2_ref(tasks, +1); - ao2_rdlock(tasks); - i = ao2_iterator_init(tasks, 0); - while ((schtd = ao2_iterator_next(&i))) { - int next_run_sec = ast_sip_sched_task_get_next_run(schtd) / 1000; - struct timeval next = ast_tvadd(now, (struct timeval) {next_run_sec, 0}); + next_run_sec = ast_sip_sched_task_get_next_run(schtd) / 1000; + if (next_run_sec < 0) { + /* Scheduled task is now canceled */ + ao2_unlock(schtd); + continue; + } + next = ast_tvadd(now, ast_tv(next_run_sec, 0)); ast_localtime(&schtd->when_queued, &tm, NULL); ast_strftime(queued, sizeof(queued), log_format, &tm); @@ -426,7 +528,7 @@ static char *cli_show_tasks(struct ast_cli_entry *e, int cmd, struct ast_cli_arg ast_localtime(&next, &tm, NULL); ast_strftime(next_start, sizeof(next_start), log_format, &tm); - ast_cli(a->fd, " %1$-24.24s %2$9.3f %3$9d %4$-5s %6$-*5$s %7$-*5$s %8$-*5$s (%9$5d)\n", + ast_cli(a->fd, "%1$-46.46s%2$9.3f %3$9d %4$-5s %6$-*5$s %7$-*5$s %8$-*5$s (%9$5d)\n", schtd->name, schtd->interval / 1000.0, schtd->run_count, @@ -434,11 +536,10 @@ static char *cli_show_tasks(struct ast_cli_entry *e, int cmd, struct ast_cli_arg datelen, queued, last_start, next_start, next_run_sec); - ao2_cleanup(schtd); + ao2_unlock(schtd); } - ao2_iterator_destroy(&i); - ao2_unlock(tasks); - ao2_ref(tasks, -1); + ao2_iterator_destroy(&iter); + ao2_ref(sorted_tasks, -1); ast_cli(a->fd, "\n"); return CLI_SUCCESS; @@ -450,7 +551,8 @@ static struct ast_cli_entry cli_commands[] = { int ast_sip_initialize_scheduler(void) { - if (!(scheduler_context = ast_sched_context_create())) { + scheduler_context = ast_sched_context_create(); + if (!scheduler_context) { ast_log(LOG_ERROR, "Failed to create scheduler. Aborting load\n"); return -1; } @@ -461,8 +563,9 @@ int ast_sip_initialize_scheduler(void) return -1; } - tasks = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_RWLOCK, AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT, - TASK_BUCKETS, ast_sip_sched_task_hash_fn, ast_sip_sched_task_sort_fn, ast_sip_sched_task_cmp_fn); + tasks = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_RWLOCK, + AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT, TASK_BUCKETS, ast_sip_sched_task_hash_fn, + ast_sip_sched_task_sort_fn, ast_sip_sched_task_cmp_fn); if (!tasks) { ast_log(LOG_ERROR, "Failed to allocate task container. Aborting load\n"); ast_sched_context_destroy(scheduler_context); @@ -479,7 +582,21 @@ int ast_sip_destroy_scheduler(void) ast_cli_unregister_multiple(cli_commands, ARRAY_LEN(cli_commands)); if (scheduler_context) { + if (tasks) { + struct ao2_iterator iter; + struct ast_sip_sched_task *schtd; + + /* Cancel all scheduled tasks */ + iter = ao2_iterator_init(tasks, 0); + while ((schtd = ao2_iterator_next(&iter))) { + ast_sip_sched_task_cancel(schtd); + ao2_ref(schtd, -1); + } + ao2_iterator_destroy(&iter); + } + ast_sched_context_destroy(scheduler_context); + scheduler_context = NULL; } ao2_cleanup(tasks); diff --git a/res/res_pjsip_header_funcs.c b/res/res_pjsip_header_funcs.c index 6c0f9151d..798a1cde6 100644 --- a/res/res_pjsip_header_funcs.c +++ b/res/res_pjsip_header_funcs.c @@ -153,7 +153,7 @@ static const struct ast_datastore_info header_datastore = { .type = "header_datastore", }; -/*! \brief Data structure used for ast_sip_push_task_synchronous */ +/*! \brief Data structure used for ast_sip_push_task_wait_serializer */ struct header_data { struct ast_sip_channel_pvt *channel; char *header_name; @@ -480,11 +480,11 @@ static int func_read_header(struct ast_channel *chan, const char *function, char header_data.len = len; if (!strcasecmp(args.action, "read")) { - return ast_sip_push_task_synchronous(channel->session->serializer, read_header, - &header_data); + return ast_sip_push_task_wait_serializer(channel->session->serializer, + read_header, &header_data); } else if (!strcasecmp(args.action, "remove")) { - return ast_sip_push_task_synchronous(channel->session->serializer, remove_header, - &header_data); + return ast_sip_push_task_wait_serializer(channel->session->serializer, + remove_header, &header_data); } else { ast_log(AST_LOG_ERROR, "Unknown action '%s' is not valid, must be 'read' or 'remove'.\n", @@ -539,14 +539,14 @@ static int func_write_header(struct ast_channel *chan, const char *cmd, char *da header_data.len = 0; if (!strcasecmp(args.action, "add")) { - return ast_sip_push_task_synchronous(channel->session->serializer, add_header, - &header_data); + return ast_sip_push_task_wait_serializer(channel->session->serializer, + add_header, &header_data); } else if (!strcasecmp(args.action, "update")) { - return ast_sip_push_task_synchronous(channel->session->serializer, update_header, - &header_data); + return ast_sip_push_task_wait_serializer(channel->session->serializer, + update_header, &header_data); } else if (!strcasecmp(args.action, "remove")) { - return ast_sip_push_task_synchronous(channel->session->serializer, remove_header, - &header_data); + return ast_sip_push_task_wait_serializer(channel->session->serializer, + remove_header, &header_data); } else { ast_log(AST_LOG_ERROR, "Unknown action '%s' is not valid, must be 'add', 'update', or 'remove'.\n", diff --git a/res/res_pjsip_history.c b/res/res_pjsip_history.c index ab035a296..eed06eed8 100644 --- a/res/res_pjsip_history.c +++ b/res/res_pjsip_history.c @@ -1385,7 +1385,7 @@ static int unload_module(void) ast_cli_unregister_multiple(cli_pjsip, ARRAY_LEN(cli_pjsip)); ast_sip_unregister_service(&logging_module); - ast_sip_push_task_synchronous(NULL, clear_history_entries, NULL); + ast_sip_push_task_wait_servant(NULL, clear_history_entries, NULL); AST_VECTOR_FREE(&vector_history); ast_pjproject_caching_pool_destroy(&cachingpool); diff --git a/res/res_pjsip_notify.c b/res/res_pjsip_notify.c index 253cf9ac8..98a75c964 100644 --- a/res/res_pjsip_notify.c +++ b/res/res_pjsip_notify.c @@ -25,6 +25,7 @@ #include "asterisk.h" #include <pjsip.h> +#include <pjsip_ua.h> #include "asterisk/cli.h" #include "asterisk/config.h" @@ -32,12 +33,13 @@ #include "asterisk/module.h" #include "asterisk/pbx.h" #include "asterisk/res_pjsip.h" +#include "asterisk/res_pjsip_session.h" #include "asterisk/sorcery.h" /*** DOCUMENTATION <manager name="PJSIPNotify" language="en_US"> <synopsis> - Send a NOTIFY to either an endpoint or an arbitrary URI. + Send a NOTIFY to either an endpoint, an arbitrary URI, or inside a SIP dialog. </synopsis> <syntax> <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" /> @@ -47,6 +49,9 @@ <parameter name="URI" required="false"> <para>Abritrary URI to which to send the NOTIFY.</para> </parameter> + <parameter name="channel" required="false"> + <para>Channel name to send the NOTIFY. Must be a PJSIP channel.</para> + </parameter> <parameter name="Variable" required="true"> <para>Appends variables as headers/content to the NOTIFY. If the variable is named <literal>Content</literal>, then the value will compose the body @@ -55,14 +60,14 @@ </parameter> </syntax> <description> - <para>Sends a NOTIFY to an endpoint or an arbitrary URI.</para> + <para>Sends a NOTIFY to an endpoint, an arbitrary URI, or inside a SIP dialog.</para> <para>All parameters for this event must be specified in the body of this request via multiple <literal>Variable: name=value</literal> sequences.</para> - <note><para>One (and only one) of <literal>Endpoint</literal> or - <literal>URI</literal> must be specified. If <literal>URI</literal> is used, - the default outbound endpoint will be used to send the message. If the default - outbound endpoint isn't configured, this command can not send to an arbitrary - URI.</para></note> + <note><para>One (and only one) of <literal>Endpoint</literal>, + <literal>URI</literal>, or <literal>Channel</literal> must be specified. + If <literal>URI</literal> is used, the default outbound endpoint will be used + to send the message. If the default outbound endpoint isn't configured, this command + can not send to an arbitrary URI.</para></note> </description> </manager> <configInfo name="res_pjsip_notify" language="en_US"> @@ -289,6 +294,16 @@ struct notify_uri_data { void (*build_notify)(pjsip_tx_data *, void *); }; +/*! + * \internal + * \brief Structure to hold task data for notifications (channel variant) + */ +struct notify_channel_data { + struct ast_sip_session *session; + void *info; + void (*build_notify)(pjsip_tx_data *, void *); +}; + static void notify_cli_uri_data_destroy(void *obj) { struct notify_uri_data *data = obj; @@ -381,6 +396,19 @@ static void notify_ami_uri_data_destroy(void *obj) ast_variables_destroy(info); } +/*! + * \internal + * \brief Destroy the notify AMI channel data releasing any resources. + */ +static void notify_ami_channel_data_destroy(void *obj) +{ + struct notify_channel_data *data = obj; + struct ast_variable *info = data->info; + + ao2_cleanup(data->session); + ast_variables_destroy(info); +} + static void build_ami_notify(pjsip_tx_data *tdata, void *info); /*! @@ -432,6 +460,28 @@ static struct notify_uri_data* notify_ami_uri_data_create( /*! * \internal + * \brief Construct a notify channel data object for AMI. + */ +static struct notify_channel_data *notify_ami_channel_data_create( + struct ast_sip_session *session, void *info) +{ + struct notify_channel_data *data; + + data = ao2_alloc_options(sizeof(*data), notify_ami_channel_data_destroy, + AO2_ALLOC_OPT_LOCK_NOLOCK); + if (!data) { + return NULL; + } + + data->session = session; + data->info = info; + data->build_notify = build_ami_notify; + + return data; +} + +/*! + * \internal * \brief Checks if the given header name is not allowed. * * \details Some headers are not allowed to be set by the user within the @@ -672,9 +722,45 @@ static int notify_uri(void *obj) return 0; } +/*! + * \internal + * \brief Send a notify request to a channel. + */ +static int notify_channel(void *obj) +{ + RAII_VAR(struct notify_channel_data *, data, obj, ao2_cleanup); + pjsip_tx_data *tdata; + struct pjsip_dialog *dlg; + + if (!data->session->channel + || !data->session->inv_session + || data->session->inv_session->state < PJSIP_INV_STATE_EARLY + || data->session->inv_session->state == PJSIP_INV_STATE_DISCONNECTED) { + return -1; + } + + ast_debug(1, "Sending notify on channel %s\n", ast_channel_name(data->session->channel)); + + dlg = data->session->inv_session->dlg; + + if (ast_sip_create_request("NOTIFY", dlg, NULL, NULL, NULL, &tdata)) { + return -1; + } + + ast_sip_add_header(tdata, "Subscription-State", "terminated"); + data->build_notify(tdata, data->info); + + if (ast_sip_send_request(tdata, dlg, NULL, NULL, NULL)) { + return -1; + } + + return 0; +} + enum notify_result { SUCCESS, INVALID_ENDPOINT, + INVALID_CHANNEL, ALLOC_ERROR, TASK_PUSH_ERROR }; @@ -684,6 +770,10 @@ typedef struct notify_data *(*task_data_create)( typedef struct notify_uri_data *(*task_uri_data_create)( const char *uri, void *info); + +typedef struct notify_channel_data *(*task_channel_data_create)( + struct ast_sip_session *session, void *info); + /*! * \internal * \brief Send a NOTIFY request to the endpoint within a threaded task. @@ -734,6 +824,68 @@ static enum notify_result push_notify_uri(const char *uri, void *info, /*! * \internal + * \brief Send a NOTIFY request in a channel within an threaded task. + */ +static enum notify_result push_notify_channel(const char *channel_name, void *info, + task_channel_data_create data_create) +{ + struct notify_channel_data *data; + struct ast_channel *ch; + struct ast_sip_session *session; + struct ast_sip_channel_pvt *ch_pvt; + + /* note: this increases the refcount of the channel */ + ch = ast_channel_get_by_name(channel_name); + if (!ch) { + ast_debug(1, "No channel found with name %s", channel_name); + return INVALID_CHANNEL; + } + + if (strcmp(ast_channel_tech(ch)->type, "PJSIP")) { + ast_log(LOG_WARNING, "Channel was a non-PJSIP channel: %s\n", channel_name); + ast_channel_unref(ch); + return INVALID_CHANNEL; + } + + ast_channel_lock(ch); + ch_pvt = ast_channel_tech_pvt(ch); + session = ch_pvt->session; + + if (!session || !session->inv_session + || session->inv_session->state < PJSIP_INV_STATE_EARLY + || session->inv_session->state == PJSIP_INV_STATE_DISCONNECTED) { + ast_debug(1, "No active session for channel %s\n", channel_name); + ast_channel_unlock(ch); + ast_channel_unref(ch); + return INVALID_CHANNEL; + } + + ao2_ref(session, +1); + ast_channel_unlock(ch); + + /* don't keep a reference to the channel, we've got a reference to the session */ + ast_channel_unref(ch); + + /* + * data_create will take ownership of the session, + * and take care of releasing the ref. + */ + data = data_create(session, info); + if (!data) { + ao2_ref(session, -1); + return ALLOC_ERROR; + } + + if (ast_sip_push_task(session->serializer, notify_channel, data)) { + ao2_ref(data, -1); + return TASK_PUSH_ERROR; + } + + return SUCCESS; +} + +/*! + * \internal * \brief Do completion on the endpoint. */ static char *cli_complete_endpoint(const char *word, int state) @@ -915,6 +1067,10 @@ static void manager_notify_endpoint(struct mansession *s, } switch (push_notify(endpoint_name, vars, notify_ami_data_create)) { + case INVALID_CHANNEL: + /* Shouldn't be possible. */ + ast_assert(0); + break; case INVALID_ENDPOINT: ast_variables_destroy(vars); astman_send_error_va(s, m, "Unable to retrieve endpoint %s", @@ -944,6 +1100,10 @@ static void manager_notify_uri(struct mansession *s, struct ast_variable *vars = astman_get_variables_order(m, ORDER_NATURAL); switch (push_notify_uri(uri, vars, notify_ami_uri_data_create)) { + case INVALID_CHANNEL: + /* Shouldn't be possible. */ + ast_assert(0); + break; case INVALID_ENDPOINT: /* Shouldn't be possible. */ ast_assert(0); @@ -964,22 +1124,70 @@ static void manager_notify_uri(struct mansession *s, /*! * \internal + * \brief Completes SIPNotify AMI command in channel mode. + */ +static void manager_notify_channel(struct mansession *s, + const struct message *m, const char *channel) +{ + struct ast_variable *vars = astman_get_variables_order(m, ORDER_NATURAL); + + switch (push_notify_channel(channel, vars, notify_ami_channel_data_create)) { + case INVALID_CHANNEL: + ast_variables_destroy(vars); + astman_send_error(s, m, "Channel not found"); + break; + case INVALID_ENDPOINT: + /* Shouldn't be possible. */ + ast_assert(0); + break; + case ALLOC_ERROR: + ast_variables_destroy(vars); + astman_send_error(s, m, "Unable to allocate NOTIFY task data"); + break; + case TASK_PUSH_ERROR: + /* Don't need to destroy vars since it is handled by cleanup in push_notify_channel */ + astman_send_error(s, m, "Unable to push Notify task"); + break; + case SUCCESS: + astman_send_ack(s, m, "NOTIFY sent"); + break; + } +} + +/*! + * \internal * \brief AMI entry point to send a SIP notify to an endpoint. */ static int manager_notify(struct mansession *s, const struct message *m) { const char *endpoint_name = astman_get_header(m, "Endpoint"); const char *uri = astman_get_header(m, "URI"); + const char *channel = astman_get_header(m, "Channel"); + int count = 0; + + if (!ast_strlen_zero(endpoint_name)) { + ++count; + } + if (!ast_strlen_zero(uri)) { + ++count; + } + if (!ast_strlen_zero(channel)) { + ++count; + } - if (!ast_strlen_zero(endpoint_name) && !ast_strlen_zero(uri)) { - astman_send_error(s, m, "PJSIPNotify action can not handle a request specifying " - "both 'URI' and 'Endpoint'. You must use only one of the two.\n"); + if (1 < count) { + astman_send_error(s, m, + "PJSIPNotify requires either an endpoint name, a SIP URI, or a channel. " + "You must use only one of them."); } else if (!ast_strlen_zero(endpoint_name)) { manager_notify_endpoint(s, m, endpoint_name); } else if (!ast_strlen_zero(uri)) { manager_notify_uri(s, m, uri); + } else if (!ast_strlen_zero(channel)) { + manager_notify_channel(s, m, channel); } else { - astman_send_error(s, m, "PJSIPNotify requires either an endpoint name or a SIP URI."); + astman_send_error(s, m, + "PJSIPNotify requires either an endpoint name, a SIP URI, or a channel."); } return 0; diff --git a/res/res_pjsip_outbound_publish.c b/res/res_pjsip_outbound_publish.c index 8befbc1e8..4894e55d1 100644 --- a/res/res_pjsip_outbound_publish.c +++ b/res/res_pjsip_outbound_publish.c @@ -1070,7 +1070,7 @@ static struct sip_outbound_publisher *sip_outbound_publisher_alloc( return NULL; } - if (ast_sip_push_task_synchronous(NULL, sip_outbound_publisher_init, publisher)) { + if (ast_sip_push_task_wait_servant(NULL, sip_outbound_publisher_init, publisher)) { ast_log(LOG_ERROR, "Unable to create publisher for outbound publish '%s'\n", ast_sorcery_object_get_id(client->publish)); ao2_ref(publisher, -1); @@ -1514,8 +1514,8 @@ static int current_state_reusable(struct ast_sip_outbound_publish *publish, */ old_publish = current_state->client->publish; current_state->client->publish = publish; - if (ast_sip_push_task_synchronous( - NULL, sip_outbound_publisher_reinit_all, current_state->client->publishers)) { + if (ast_sip_push_task_wait_servant(NULL, sip_outbound_publisher_reinit_all, + current_state->client->publishers)) { /* * If the state object fails to re-initialize then swap * the old publish info back in. diff --git a/res/res_pjsip_outbound_registration.c b/res/res_pjsip_outbound_registration.c index 2839ecbab..0d815ad39 100644 --- a/res/res_pjsip_outbound_registration.c +++ b/res/res_pjsip_outbound_registration.c @@ -834,6 +834,8 @@ static int reregister_immediately_cb(void *obj) * * \param obj What is needed to initiate a reregister attempt. * + * \note Normally executed by the pjsip monitor thread. + * * \return Nothing */ static void registration_transport_shutdown_cb(void *obj) @@ -1480,7 +1482,7 @@ static int sip_outbound_registration_apply(const struct ast_sorcery *sorcery, vo return -1; } - if (ast_sip_push_task_synchronous(new_state->client_state->serializer, + if (ast_sip_push_task_wait_serializer(new_state->client_state->serializer, sip_outbound_registration_regc_alloc, new_state)) { return -1; } @@ -1850,8 +1852,7 @@ static int ami_outbound_registration_detail(void *obj, void *arg, int flags) struct sip_ami_outbound *ami = arg; ami->registration = obj; - return ast_sip_push_task_synchronous( - NULL, ami_outbound_registration_task, ami); + return ast_sip_push_task_wait_servant(NULL, ami_outbound_registration_task, ami); } static int ami_show_outbound_registrations(struct mansession *s, diff --git a/res/res_pjsip_pubsub.c b/res/res_pjsip_pubsub.c index 69c256dab..d98491495 100644 --- a/res/res_pjsip_pubsub.c +++ b/res/res_pjsip_pubsub.c @@ -560,15 +560,52 @@ static void *publication_resource_alloc(const char *name) return ast_sorcery_generic_alloc(sizeof(struct ast_sip_publication_resource), publication_resource_destroy); } -static void sub_tree_transport_cb(void *data) { +static int sub_tree_subscription_terminate_cb(void *data) +{ struct sip_subscription_tree *sub_tree = data; - ast_debug(3, "Transport destroyed. Removing subscription '%s->%s' prune on restart: %d\n", + if (!sub_tree->evsub) { + /* Something else already terminated the subscription. */ + ao2_ref(sub_tree, -1); + return 0; + } + + ast_debug(3, "Transport destroyed. Removing subscription '%s->%s' prune on boot: %d\n", sub_tree->persistence->endpoint, sub_tree->root->resource, sub_tree->persistence->prune_on_boot); sub_tree->state = SIP_SUB_TREE_TERMINATE_IN_PROGRESS; pjsip_evsub_terminate(sub_tree->evsub, PJ_TRUE); + + ao2_ref(sub_tree, -1); + return 0; +} + +/*! + * \internal + * \brief The reliable transport we used as a subscription contact has shutdown. + * + * \param data What subscription needs to be terminated. + * + * \note Normally executed by the pjsip monitor thread. + * + * \return Nothing + */ +static void sub_tree_transport_cb(void *data) +{ + struct sip_subscription_tree *sub_tree = data; + + /* + * Push off the subscription termination to the serializer to + * avoid deadlock. Another thread could be trying to send a + * message on the subscription that can deadlock with this + * thread. + */ + ao2_ref(sub_tree, +1); + if (ast_sip_push_task(sub_tree->serializer, sub_tree_subscription_terminate_cb, + sub_tree)) { + ao2_ref(sub_tree, -1); + } } /*! \brief Destructor for subscription persistence */ @@ -621,7 +658,7 @@ static void subscription_persistence_update(struct sip_subscription_tree *sub_tr return; } - ast_debug(3, "Updating persistence for '%s->%s' prune on restart: %s\n", + ast_debug(3, "Updating persistence for '%s->%s' prune on boot: %s\n", sub_tree->persistence->endpoint, sub_tree->root->resource, sub_tree->persistence->prune_on_boot ? "yes" : "no"); @@ -645,7 +682,7 @@ static void subscription_persistence_update(struct sip_subscription_tree *sub_tr sub_tree->endpoint, rdata); if (sub_tree->persistence->prune_on_boot) { - ast_debug(3, "adding transport monitor on %s for '%s->%s' prune on restart: %d\n", + ast_debug(3, "adding transport monitor on %s for '%s->%s' prune on boot: %d\n", rdata->tp_info.transport->obj_name, sub_tree->persistence->endpoint, sub_tree->root->resource, sub_tree->persistence->prune_on_boot); @@ -1318,7 +1355,8 @@ static void subscription_tree_destructor(void *obj) destroy_subscriptions(sub_tree->root); if (sub_tree->dlg) { - ast_sip_push_task_synchronous(sub_tree->serializer, subscription_unreference_dialog, sub_tree); + ast_sip_push_task_wait_servant(sub_tree->serializer, + subscription_unreference_dialog, sub_tree); } ao2_cleanup(sub_tree->endpoint); @@ -1665,7 +1703,8 @@ static int subscription_persistence_recreate(void *obj, void *arg, int flags) } recreate_data.persistence = persistence; recreate_data.rdata = &rdata; - if (ast_sip_push_task_synchronous(serializer, sub_persistence_recreate, &recreate_data)) { + if (ast_sip_push_task_wait_serializer(serializer, sub_persistence_recreate, + &recreate_data)) { ast_log(LOG_WARNING, "Failed recreating '%s' subscription: Could not continue under distributor serializer.\n", persistence->endpoint); ast_sorcery_delete(ast_sip_get_sorcery(), persistence); diff --git a/res/res_pjsip_refer.c b/res/res_pjsip_refer.c index 7d892f653..1e6ca7f46 100644 --- a/res/res_pjsip_refer.c +++ b/res/res_pjsip_refer.c @@ -316,7 +316,15 @@ static void refer_progress_on_evsub_state(pjsip_evsub *sub, pjsip_event *event) /* It's possible that a task is waiting to remove us already, so bump the refcount of progress so it doesn't get destroyed */ ao2_ref(progress, +1); pjsip_dlg_dec_lock(progress->dlg); - ast_sip_push_task_synchronous(progress->serializer, refer_progress_terminate, progress); + /* + * XXX We are always going to execute this inline rather than + * in the serializer because this function is a PJPROJECT + * callback and thus has to be a SIP servant thread. + * + * The likely remedy is to push most of this function into + * refer_progress_terminate() with ast_sip_push_task(). + */ + ast_sip_push_task_wait_servant(progress->serializer, refer_progress_terminate, progress); pjsip_dlg_inc_lock(progress->dlg); ao2_ref(progress, -1); @@ -917,10 +925,7 @@ static int invite_replaces(void *data) ast_channel_ref(invite->session->channel); invite->channel = invite->session->channel; - ast_channel_lock(invite->channel); - invite->bridge = ast_channel_get_bridge(invite->channel); - ast_channel_unlock(invite->channel); - + invite->bridge = ast_bridge_transfer_acquire_bridge(invite->channel); return 0; } @@ -963,7 +968,8 @@ static int refer_incoming_invite_request(struct ast_sip_session *session, struct invite.session = other_session; - if (ast_sip_push_task_synchronous(other_session->serializer, invite_replaces, &invite)) { + if (ast_sip_push_task_wait_serializer(other_session->serializer, invite_replaces, + &invite)) { response = 481; goto inv_replace_failed; } diff --git a/res/res_pjsip_registrar.c b/res/res_pjsip_registrar.c index bdee91fb3..985933e2d 100644 --- a/res/res_pjsip_registrar.c +++ b/res/res_pjsip_registrar.c @@ -337,7 +337,7 @@ static int contact_transport_monitor_matcher(void *a, void *b) && strcmp(ma->contact_name, mb->contact_name) == 0; } -static void register_contact_transport_shutdown_cb(void *data) +static int register_contact_transport_remove_cb(void *data) { struct contact_transport_monitor *monitor = data; struct ast_sip_contact *contact; @@ -345,7 +345,8 @@ static void register_contact_transport_shutdown_cb(void *data) aor = ast_sip_location_retrieve_aor(monitor->aor_name); if (!aor) { - return; + ao2_ref(monitor, -1); + return 0; } ao2_lock(aor); @@ -365,6 +366,35 @@ static void register_contact_transport_shutdown_cb(void *data) } ao2_unlock(aor); ao2_ref(aor, -1); + + ao2_ref(monitor, -1); + return 0; +} + +/*! + * \internal + * \brief The reliable transport we registered as a contact has shutdown. + * + * \param data What contact needs to be removed. + * + * \note Normally executed by the pjsip monitor thread. + * + * \return Nothing + */ +static void register_contact_transport_shutdown_cb(void *data) +{ + struct contact_transport_monitor *monitor = data; + + /* + * Push off to a default serializer. This is in case sorcery + * does database accesses for contacts. Database accesses may + * not be on this machine. We don't want to tie up the pjsip + * monitor thread with potentially long access times. + */ + ao2_ref(monitor, +1); + if (ast_sip_push_task(NULL, register_contact_transport_remove_cb, monitor)) { + ao2_ref(monitor, -1); + } } AST_VECTOR(excess_contact_vector, struct ast_sip_contact *); diff --git a/res/res_pjsip_sdp_rtp.c b/res/res_pjsip_sdp_rtp.c index 03d37652f..14ed3b186 100644 --- a/res/res_pjsip_sdp_rtp.c +++ b/res/res_pjsip_sdp_rtp.c @@ -1096,6 +1096,9 @@ static void add_rtcp_fb_to_stream(struct ast_sip_session *session, attr = pjmedia_sdp_attr_create(pool, "rtcp-fb", pj_cstr(&stmp, "* goog-remb")); pjmedia_sdp_attr_add(&media->attr_count, media->attr, attr); + + attr = pjmedia_sdp_attr_create(pool, "rtcp-fb", pj_cstr(&stmp, "* nack")); + pjmedia_sdp_attr_add(&media->attr_count, media->attr, attr); } /*! \brief Function which negotiates an incoming media stream */ diff --git a/res/res_pjsip_session.c b/res/res_pjsip_session.c index d13b372be..49ab87568 100644 --- a/res/res_pjsip_session.c +++ b/res/res_pjsip_session.c @@ -4153,7 +4153,7 @@ static void session_outgoing_nat_hook(pjsip_tx_data *tdata, struct ast_sip_trans if (ast_sip_transport_is_local(transport_state, &our_sdp_addr) || !transport_state->localnet) { ast_debug(5, "Setting external media address to %s\n", ast_sockaddr_stringify_host(&transport_state->external_media_address)); pj_strdup2(tdata->pool, &sdp->conn->addr, ast_sockaddr_stringify_host(&transport_state->external_media_address)); - pj_strdup2(tdata->pool, &sdp->origin.addr, transport->external_media_address); + pj_strassign(&sdp->origin.addr, &sdp->conn->addr); } } diff --git a/res/res_pjsip_transport_websocket.c b/res/res_pjsip_transport_websocket.c index 974b15087..633594359 100644 --- a/res/res_pjsip_transport_websocket.c +++ b/res/res_pjsip_transport_websocket.c @@ -377,7 +377,7 @@ static void websocket_cb(struct ast_websocket *session, struct ast_variable *par create_data.ws_session = session; - if (ast_sip_push_task_synchronous(serializer, transport_create, &create_data)) { + if (ast_sip_push_task_wait_serializer(serializer, transport_create, &create_data)) { ast_log(LOG_ERROR, "Could not create WebSocket transport.\n"); ast_taskprocessor_unreference(serializer); ast_websocket_unref(session); @@ -396,13 +396,13 @@ static void websocket_cb(struct ast_websocket *session, struct ast_variable *par } if (opcode == AST_WEBSOCKET_OPCODE_TEXT || opcode == AST_WEBSOCKET_OPCODE_BINARY) { - ast_sip_push_task_synchronous(serializer, transport_read, &read_data); + ast_sip_push_task_wait_serializer(serializer, transport_read, &read_data); } else if (opcode == AST_WEBSOCKET_OPCODE_CLOSE) { break; } } - ast_sip_push_task_synchronous(serializer, transport_shutdown, transport); + ast_sip_push_task_wait_serializer(serializer, transport_shutdown, transport); ast_taskprocessor_unreference(serializer); ast_websocket_unref(session); diff --git a/res/res_rtp_asterisk.c b/res/res_rtp_asterisk.c index b010f6c51..4ac20d551 100644 --- a/res/res_rtp_asterisk.c +++ b/res/res_rtp_asterisk.c @@ -71,6 +71,7 @@ #include "asterisk/smoother.h" #include "asterisk/uuid.h" #include "asterisk/test.h" +#include "asterisk/data_buffer.h" #ifdef HAVE_PJPROJECT #include "asterisk/res_pjproject.h" #endif @@ -92,6 +93,8 @@ #define TURN_STATE_WAIT_TIME 2000 +#define DEFAULT_RTP_BUFFER_SIZE 250 + /*! Full INTRA-frame Request / Fast Update Request (From RFC2032) */ #define RTCP_PT_FUR 192 /*! Sender Report (From RFC3550) */ @@ -373,6 +376,8 @@ struct ast_rtp { struct rtp_red *red; + struct ast_data_buffer *send_buffer; /*!< Buffer for storing sent packets for retransmission */ + #ifdef HAVE_PJPROJECT ast_cond_t cond; /*!< ICE/TURN condition for signaling */ @@ -509,6 +514,12 @@ struct rtp_red { long int prev_ts; }; +/*! \brief Structure for storing RTP packets for retransmission */ +struct ast_rtp_rtcp_nack_payload { + size_t size; /*!< The size of the payload */ + unsigned char buf[0]; /*!< The payload data */ +}; + AST_LIST_HEAD_NOLOCK(frame_list, ast_frame); /* Forward Declarations */ @@ -3675,6 +3686,11 @@ static int ast_rtp_destroy(struct ast_rtp_instance *instance) rtp->red = NULL; } + /* Destroy the send buffer if it was being used */ + if (rtp->send_buffer) { + ast_data_buffer_free(rtp->send_buffer); + } + ao2_cleanup(rtp->lasttxformat); ao2_cleanup(rtp->lastrxformat); ao2_cleanup(rtp->f.subclass.format); @@ -4369,7 +4385,7 @@ static int rtp_raw_write(struct ast_rtp_instance *instance, struct ast_frame *fr } else { /* This is the first frame with sequence number we've seen, so start keeping track */ rtp->expectedseqno = frame->seqno + 1; - } + } } else { rtp->expectedseqno = -1; } @@ -4383,13 +4399,27 @@ static int rtp_raw_write(struct ast_rtp_instance *instance, struct ast_frame *fr /* If we know the remote address construct a packet and send it out */ if (!ast_sockaddr_isnull(&remote_address)) { int hdrlen = 12, res, ice; + int packet_len = frame->datalen + hdrlen; unsigned char *rtpheader = (unsigned char *)(frame->data.ptr - hdrlen); put_unaligned_uint32(rtpheader, htonl((2 << 30) | (codec << 16) | (seqno) | (mark << 23))); put_unaligned_uint32(rtpheader + 4, htonl(rtp->lastts)); put_unaligned_uint32(rtpheader + 8, htonl(rtp->ssrc)); - if ((res = rtp_sendto(instance, (void *)rtpheader, frame->datalen + hdrlen, 0, &remote_address, &ice)) < 0) { + /* If retransmissions are enabled, we need to store this packet for future use */ + if (rtp->send_buffer) { + struct ast_rtp_rtcp_nack_payload *payload; + + payload = ast_malloc(sizeof(*payload) + packet_len); + if (payload) { + payload->size = packet_len; + memcpy(payload->buf, rtpheader, packet_len); + ast_data_buffer_put(rtp->send_buffer, rtp->seqno, payload); + } + } + + res = rtp_sendto(instance, (void *)rtpheader, packet_len, 0, &remote_address, &ice); + if (res < 0) { if (!ast_rtp_instance_get_prop(instance, AST_RTP_PROPERTY_NAT) || (ast_rtp_instance_get_prop(instance, AST_RTP_PROPERTY_NAT) && (ast_test_flag(rtp, FLAG_NAT_ACTIVE) == FLAG_NAT_ACTIVE))) { ast_debug(1, "RTP Transmission error of packet %d to %s: %s\n", rtp->seqno, @@ -4473,6 +4503,94 @@ static struct ast_frame *red_t140_to_red(struct rtp_red *red) return &red->t140red; } +static void rtp_write_rtcp_fir(struct ast_rtp_instance *instance, struct ast_rtp *rtp, struct ast_sockaddr *remote_address) +{ + unsigned int *rtcpheader; + char bdata[1024]; + int len = 20; + int ice; + int res; + + if (!rtp || !rtp->rtcp) { + return; + } + + if (ast_sockaddr_isnull(&rtp->rtcp->them) || rtp->rtcp->schedid < 0) { + /* + * RTCP was stopped. + */ + return; + } + + if (!rtp->themssrc_valid) { + /* We don't know their SSRC value so we don't know who to update. */ + return; + } + + /* Prepare RTCP FIR (PT=206, FMT=4) */ + rtp->rtcp->firseq++; + if(rtp->rtcp->firseq == 256) { + rtp->rtcp->firseq = 0; + } + + rtcpheader = (unsigned int *)bdata; + rtcpheader[0] = htonl((2 << 30) | (4 << 24) | (RTCP_PT_PSFB << 16) | ((len/4)-1)); + rtcpheader[1] = htonl(rtp->ssrc); + rtcpheader[2] = htonl(rtp->themssrc); + rtcpheader[3] = htonl(rtp->themssrc); /* FCI: SSRC */ + rtcpheader[4] = htonl(rtp->rtcp->firseq << 24); /* FCI: Sequence number */ + res = rtcp_sendto(instance, (unsigned int *)rtcpheader, len, 0, rtp->bundled ? remote_address : &rtp->rtcp->them, &ice); + if (res < 0) { + ast_log(LOG_ERROR, "RTCP FIR transmission error: %s\n", strerror(errno)); + } +} + +static void rtp_write_rtcp_psfb(struct ast_rtp_instance *instance, struct ast_rtp *rtp, struct ast_frame *frame, struct ast_sockaddr *remote_address) +{ + struct ast_rtp_rtcp_feedback *feedback = frame->data.ptr; + unsigned int *rtcpheader; + char bdata[1024]; + int len = 24; + int ice; + int res; + + if (feedback->fmt != AST_RTP_RTCP_FMT_REMB) { + ast_debug(1, "Provided an RTCP feedback frame of format %d to write on RTP instance '%p' but only REMB is supported\n", + feedback->fmt, instance); + return; + } + + if (!rtp || !rtp->rtcp) { + return; + } + + /* If REMB support is not enabled don't send this RTCP packet */ + if (!ast_rtp_instance_get_prop(instance, AST_RTP_PROPERTY_REMB)) { + ast_debug(1, "Provided an RTCP feedback REMB report to write on RTP instance '%p' but REMB support not enabled\n", + instance); + return; + } + + if (ast_sockaddr_isnull(&rtp->rtcp->them) || rtp->rtcp->schedid < 0) { + /* + * RTCP was stopped. + */ + return; + } + + rtcpheader = (unsigned int *)bdata; + rtcpheader[0] = htonl((2 << 30) | (AST_RTP_RTCP_FMT_REMB << 24) | (RTCP_PT_PSFB << 16) | ((len/4)-1)); + rtcpheader[1] = htonl(rtp->ssrc); + rtcpheader[2] = htonl(0); /* Per the draft this should always be 0 */ + rtcpheader[3] = htonl(('R' << 24) | ('E' << 16) | ('M' << 8) | ('B')); /* Unique identifier 'R' 'E' 'M' 'B' */ + rtcpheader[4] = htonl((1 << 24) | (feedback->remb.br_exp << 18) | (feedback->remb.br_mantissa)); /* Number of SSRCs / BR Exp / BR Mantissa */ + rtcpheader[5] = htonl(rtp->ssrc); /* The SSRC this feedback message applies to */ + res = rtcp_sendto(instance, (unsigned int *)rtcpheader, len, 0, rtp->bundled ? remote_address : &rtp->rtcp->them, &ice); + if (res < 0) { + ast_log(LOG_ERROR, "RTCP PSFB transmission error: %s\n", strerror(errno)); + } +} + /*! \pre instance is locked */ static int ast_rtp_write(struct ast_rtp_instance *instance, struct ast_frame *frame) { @@ -4491,42 +4609,11 @@ static int ast_rtp_write(struct ast_rtp_instance *instance, struct ast_frame *fr /* VP8: is this a request to send a RTCP FIR? */ if (frame->frametype == AST_FRAME_CONTROL && frame->subclass.integer == AST_CONTROL_VIDUPDATE) { - unsigned int *rtcpheader; - char bdata[1024]; - int len = 20; - int ice; - int res; - - if (!rtp || !rtp->rtcp) { - return 0; - } - - if (ast_sockaddr_isnull(&rtp->rtcp->them) || rtp->rtcp->schedid < 0) { - /* - * RTCP was stopped. - */ - return 0; - } - if (!rtp->themssrc_valid) { - /* We don't know their SSRC value so we don't know who to update. */ - return 0; - } - - /* Prepare RTCP FIR (PT=206, FMT=4) */ - rtp->rtcp->firseq++; - if(rtp->rtcp->firseq == 256) { - rtp->rtcp->firseq = 0; - } - - rtcpheader = (unsigned int *)bdata; - rtcpheader[0] = htonl((2 << 30) | (4 << 24) | (RTCP_PT_PSFB << 16) | ((len/4)-1)); - rtcpheader[1] = htonl(rtp->ssrc); - rtcpheader[2] = htonl(rtp->themssrc); - rtcpheader[3] = htonl(rtp->themssrc); /* FCI: SSRC */ - rtcpheader[4] = htonl(rtp->rtcp->firseq << 24); /* FCI: Sequence number */ - res = rtcp_sendto(instance, (unsigned int *)rtcpheader, len, 0, rtp->bundled ? &remote_address : &rtp->rtcp->them, &ice); - if (res < 0) { - ast_log(LOG_ERROR, "RTCP FIR transmission error: %s\n", strerror(errno)); + rtp_write_rtcp_fir(instance, rtp, &remote_address); + return 0; + } else if (frame->frametype == AST_FRAME_RTCP) { + if (frame->subclass.integer == AST_RTP_RTCP_PSFB) { + rtp_write_rtcp_psfb(instance, rtp, frame, &remote_address); } return 0; } @@ -5088,8 +5175,8 @@ static void update_lost_stats(struct ast_rtp *rtp, unsigned int lost_packets) } /*! \pre instance is locked */ -static struct ast_rtp_instance *rtp_find_instance_by_ssrc(struct ast_rtp_instance *instance, - struct ast_rtp *rtp, unsigned int ssrc) +static struct ast_rtp_instance *__rtp_find_instance_by_ssrc(struct ast_rtp_instance *instance, + struct ast_rtp *rtp, unsigned int ssrc, int source) { int index; @@ -5101,8 +5188,9 @@ static struct ast_rtp_instance *rtp_find_instance_by_ssrc(struct ast_rtp_instanc /* Find the bundled child instance */ for (index = 0; index < AST_VECTOR_SIZE(&rtp->ssrc_mapping); ++index) { struct rtp_ssrc_mapping *mapping = AST_VECTOR_GET_ADDR(&rtp->ssrc_mapping, index); + unsigned int mapping_ssrc = source ? ast_rtp_get_ssrc(mapping->instance) : mapping->ssrc; - if (mapping->ssrc_valid && mapping->ssrc == ssrc) { + if (mapping->ssrc_valid && mapping_ssrc == ssrc) { return mapping->instance; } } @@ -5114,6 +5202,20 @@ static struct ast_rtp_instance *rtp_find_instance_by_ssrc(struct ast_rtp_instanc return NULL; } +/*! \pre instance is locked */ +static struct ast_rtp_instance *rtp_find_instance_by_packet_source_ssrc(struct ast_rtp_instance *instance, + struct ast_rtp *rtp, unsigned int ssrc) +{ + return __rtp_find_instance_by_ssrc(instance, rtp, ssrc, 0); +} + +/*! \pre instance is locked */ +static struct ast_rtp_instance *rtp_find_instance_by_media_source_ssrc(struct ast_rtp_instance *instance, + struct ast_rtp *rtp, unsigned int ssrc) +{ + return __rtp_find_instance_by_ssrc(instance, rtp, ssrc, 1); +} + static const char *rtcp_payload_type2str(unsigned int pt) { const char *str; @@ -5146,6 +5248,69 @@ static const char *rtcp_payload_type2str(unsigned int pt) return str; } +/*! \pre instance is locked */ +static int ast_rtp_rtcp_handle_nack(struct ast_rtp_instance *instance, unsigned int *nackdata, unsigned int position, + unsigned int length) +{ + struct ast_rtp *rtp = ast_rtp_instance_get_data(instance); + int res = 0; + int blp_index; + int packet_index; + int ice; + struct ast_rtp_rtcp_nack_payload *payload; + unsigned int current_word; + unsigned int pid; /* Packet ID which refers to seqno of lost packet */ + unsigned int blp; /* Bitmask of following lost packets */ + struct ast_sockaddr remote_address = { {0,} }; + + if (!rtp->send_buffer) { + ast_debug(1, "Tried to handle NACK request, but we don't have a RTP packet storage!\n"); + return res; + } + + ast_rtp_instance_get_remote_address(instance, &remote_address); + + /* + * We use index 3 because with feedback messages, the FCI (Feedback Control Information) + * does not begin until after the version, packet SSRC, and media SSRC words. + */ + for (packet_index = 3; packet_index < length; packet_index++) { + current_word = ntohl(nackdata[position + packet_index]); + pid = current_word >> 16; + /* We know the remote end is missing this packet. Go ahead and send it if we still have it. */ + payload = (struct ast_rtp_rtcp_nack_payload *)ast_data_buffer_get(rtp->send_buffer, pid); + if (payload) { + res += rtp_sendto(instance, payload->buf, payload->size, 0, &remote_address, &ice); + } else { + ast_debug(1, "Received NACK request for RTP packet with seqno %d, but we don't have it\n", pid); + } + /* + * The bitmask. Denoting the least significant bit as 1 and its most significant bit + * as 16, then bit i of the bitmask is set to 1 if the receiver has not received RTP + * packet (pid+i)(modulo 2^16). Otherwise, it is set to 0. We cannot assume bits set + * to 0 after a bit set to 1 have actually been received. + */ + blp = current_word & 0xFF; + blp_index = 1; + while (blp) { + if (blp & 1) { + /* Packet (pid + i)(modulo 2^16) is missing too. */ + unsigned int seqno = (pid + blp_index) % 65536; + payload = (struct ast_rtp_rtcp_nack_payload *)ast_data_buffer_get(rtp->send_buffer, seqno); + if (payload) { + res += rtp_sendto(instance, payload->buf, payload->size, 0, &remote_address, &ice); + } else { + ast_debug(1, "Remote end also requested RTP packet with seqno %d, but we don't have it\n", seqno); + } + } + blp >>= 1; + blp_index++; + } + } + + return res; +} + /* * Unshifted RTCP header bit field masks */ @@ -5185,7 +5350,8 @@ static const char *rtcp_payload_type2str(unsigned int pt) #define RTCP_SR_BLOCK_WORD_LENGTH 5 #define RTCP_RR_BLOCK_WORD_LENGTH 6 #define RTCP_HEADER_SSRC_LENGTH 2 -#define RTCP_FB_REMB_BLOCK_WORD_LENGTH 5 +#define RTCP_FB_REMB_BLOCK_WORD_LENGTH 4 +#define RTCP_FB_NACK_BLOCK_WORD_LENGTH 2 static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, const unsigned char *rtcpdata, size_t size, struct ast_sockaddr *addr) { @@ -5262,6 +5428,8 @@ static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, c unsigned int ssrc_valid; unsigned int length; unsigned int min_length; + /*! Always use packet source SSRC to find the rtp instance unless explicitly told not to. */ + unsigned int use_packet_source = 1; struct ast_json *message_blob; RAII_VAR(struct ast_rtp_rtcp_report *, rtcp_report, NULL, ao2_cleanup); @@ -5284,9 +5452,20 @@ static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, c /* fall through */ case RTCP_PT_RR: min_length += (rc * RTCP_RR_BLOCK_WORD_LENGTH); + use_packet_source = 0; break; case RTCP_PT_FUR: break; + case AST_RTP_RTCP_RTPFB: + switch (rc) { + case AST_RTP_RTCP_FMT_NACK: + min_length += RTCP_FB_NACK_BLOCK_WORD_LENGTH; + break; + default: + break; + } + use_packet_source = 0; + break; case RTCP_PT_PSFB: switch (rc) { case AST_RTP_RTCP_FMT_REMB: @@ -5335,13 +5514,16 @@ static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, c } rtcp_report->reception_report_count = rc; - ssrc = ntohl(rtcpheader[i + 1]); + ssrc = ntohl(rtcpheader[i + 2]); rtcp_report->ssrc = ssrc; break; case RTCP_PT_FUR: case RTCP_PT_PSFB: ssrc = ntohl(rtcpheader[i + 1]); break; + case AST_RTP_RTCP_RTPFB: + ssrc = ntohl(rtcpheader[i + 2]); + break; case RTCP_PT_SDES: case RTCP_PT_BYE: default: @@ -5360,7 +5542,15 @@ static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, c /* Determine the appropriate instance for this */ if (ssrc_valid) { - child = rtp_find_instance_by_ssrc(transport, transport_rtp, ssrc); + /* + * Depending on the payload type, either the packet source or media source + * SSRC is used. + */ + if (use_packet_source) { + child = rtp_find_instance_by_packet_source_ssrc(transport, transport_rtp, ssrc); + } else { + child = rtp_find_instance_by_media_source_ssrc(transport, transport_rtp, ssrc); + } if (child && child != transport) { /* * It is safe to hold the child lock while holding the parent lock. @@ -5381,7 +5571,7 @@ static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, c } if (ssrc_valid && rtp->themssrc_valid) { - if (ssrc != rtp->themssrc) { + if (ssrc != rtp->themssrc && use_packet_source) { /* * Skip over this RTCP record as it does not contain the * correct SSRC. We should not act upon RTCP records @@ -5524,11 +5714,30 @@ static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, c transport_rtp->f.src = "RTP"; f = &transport_rtp->f; break; + case AST_RTP_RTCP_RTPFB: + switch (rc) { + case AST_RTP_RTCP_FMT_NACK: + /* If retransmissions are not enabled ignore this message */ + if (!rtp->send_buffer) { + break; + } + + if (rtcp_debug_test_addr(addr)) { + ast_verbose("Received generic RTCP NACK message\n"); + } + + ast_rtp_rtcp_handle_nack(instance, rtcpheader, position, length); + break; + default: + break; + } + break; case RTCP_PT_FUR: - /* Handle RTCP FUR as FIR by setting the format to 4 */ + /* Handle RTCP FUR as FIR by setting the format to 4 */ rc = AST_RTP_RTCP_FMT_FIR; case RTCP_PT_PSFB: switch (rc) { + case AST_RTP_RTCP_FMT_PLI: case AST_RTP_RTCP_FMT_FIR: if (rtcp_debug_test_addr(addr)) { ast_verbose("Received an RTCP Fast Update Request\n"); @@ -5923,7 +6132,7 @@ static struct ast_frame *ast_rtp_read(struct ast_rtp_instance *instance, int rtc ssrc = ntohl(rtpheader[2]); /* Determine the appropriate instance for this */ - child = rtp_find_instance_by_ssrc(instance, rtp, ssrc); + child = rtp_find_instance_by_packet_source_ssrc(instance, rtp, ssrc); if (!child) { /* Neither the bundled parent nor any child has this SSRC */ return &ast_null_frame; @@ -6556,6 +6765,8 @@ static void ast_rtp_prop_set(struct ast_rtp_instance *instance, enum ast_rtp_pro } } else if (property == AST_RTP_PROPERTY_ASYMMETRIC_CODEC) { rtp->asymmetric_codec = value; + } else if (property == AST_RTP_PROPERTY_RETRANS_SEND) { + rtp->send_buffer = ast_data_buffer_alloc(ast_free_ptr, DEFAULT_RTP_BUFFER_SIZE); } } diff --git a/utils/Makefile b/utils/Makefile index d62d45f4f..ae2af08e2 100644 --- a/utils/Makefile +++ b/utils/Makefile @@ -164,7 +164,7 @@ aelparse.c: $(ASTTOPDIR)/res/ael/ael_lex.c $(CMD_PREFIX) mv "$@.new" "$@" aelparse.o: _ASTCFLAGS+=-I$(ASTTOPDIR)/res -Wno-unused -aelparse: LIBS+=-lm +aelparse: LIBS+=-lm $(AST_CLANG_BLOCKS_LIBS) aelparse: aelparse.o aelbison.o pbx_ael.o hashtab.o lock.o ael_main.o ast_expr2f.o ast_expr2.o strcompat.o pval.o extconf.o astmm.o threadstorage.c: $(ASTTOPDIR)/main/threadstorage.c @@ -174,6 +174,7 @@ threadstorage.c: $(ASTTOPDIR)/main/threadstorage.c extconf.o: extconf.c +conf2ael: LIBS+=$(AST_CLANG_BLOCKS_LIBS) conf2ael: conf2ael.o ast_expr2f.o ast_expr2.o hashtab.o lock.o aelbison.o aelparse.o pbx_ael.o pval.o extconf.o strcompat.o astmm.o check_expr2: $(ASTTOPDIR)/main/ast_expr2f.c $(ASTTOPDIR)/main/ast_expr2.c $(ASTTOPDIR)/main/ast_expr2.h astmm.o |