diff options
-rw-r--r-- | apps/app_confbridge.c | 7 | ||||
-rw-r--r-- | apps/confbridge/conf_config_parser.c | 63 | ||||
-rw-r--r-- | apps/confbridge/include/confbridge.h | 3 | ||||
-rw-r--r-- | bridges/bridge_softmix.c | 242 | ||||
-rw-r--r-- | bridges/bridge_softmix/include/bridge_softmix_internal.h | 14 | ||||
-rw-r--r-- | configs/samples/confbridge.conf.sample | 4 | ||||
-rw-r--r-- | include/asterisk/bridge.h | 33 | ||||
-rw-r--r-- | include/asterisk/rtp_engine.h | 6 | ||||
-rw-r--r-- | include/asterisk/utils.h | 13 | ||||
-rw-r--r-- | main/bridge.c | 13 | ||||
-rw-r--r-- | res/res_musiconhold.c | 1 | ||||
-rw-r--r-- | res/res_pjsip_outbound_registration.c | 2 | ||||
-rw-r--r-- | res/res_pjsip_pubsub.c | 45 | ||||
-rw-r--r-- | res/res_pjsip_registrar.c | 34 | ||||
-rw-r--r-- | res/res_pjsip_sdp_rtp.c | 3 | ||||
-rw-r--r-- | res/res_rtp_asterisk.c | 171 | ||||
-rw-r--r-- | utils/Makefile | 3 |
17 files changed, 636 insertions, 21 deletions
diff --git a/apps/app_confbridge.c b/apps/app_confbridge.c index d8407d857..25cf2758f 100644 --- a/apps/app_confbridge.c +++ b/apps/app_confbridge.c @@ -1544,6 +1544,13 @@ static struct confbridge_conference *join_conference_bridge(const char *conferen 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/confbridge/conf_config_parser.c b/apps/confbridge/conf_config_parser.c index f9d74831c..c143e39e2 100644 --- a/apps/confbridge/conf_config_parser.c +++ b/apps/confbridge/conf_config_parser.c @@ -470,6 +470,27 @@ 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> @@ -1675,6 +1696,23 @@ 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)); ast_cli(a->fd,"sound_has_joined: %s\n", conf_get_sound(CONF_SOUND_HAS_JOINED, b_profile.sounds)); @@ -2011,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; @@ -2245,6 +2307,7 @@ int conf_load_config(void) 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 c2f8f9a58..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 { 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/configs/samples/confbridge.conf.sample b/configs/samples/confbridge.conf.sample index 4028593d2..8b276cdb8 100644 --- a/configs/samples/confbridge.conf.sample +++ b/configs/samples/confbridge.conf.sample @@ -239,6 +239,10 @@ type=bridge ; 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 diff --git a/include/asterisk/bridge.h b/include/asterisk/bridge.h index c96cefb60..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,9 +151,10 @@ 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; - unsigned int remb_send_interval; }; /*! @@ -917,10 +936,22 @@ void ast_bridge_set_video_update_discard(struct ast_bridge *bridge, unsigned int * * \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); diff --git a/include/asterisk/rtp_engine.h b/include/asterisk/rtp_engine.h index 8f044ce17..3426b2a1e 100644 --- a/include/asterisk/rtp_engine.h +++ b/include/asterisk/rtp_engine.h @@ -292,10 +292,14 @@ 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) */ 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 8795081e2..2b347fd3f 100644 --- a/main/bridge.c +++ b/main/bridge.c @@ -3852,8 +3852,19 @@ void ast_bridge_set_video_update_discard(struct ast_bridge *bridge, unsigned int 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.remb_send_interval = remb_send_interval; + bridge->softmix.video_mode.mode_data.sfu_data.remb_behavior = behavior; ast_bridge_unlock(bridge); } 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_outbound_registration.c b/res/res_pjsip_outbound_registration.c index 8a90849c0..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) diff --git a/res/res_pjsip_pubsub.c b/res/res_pjsip_pubsub.c index 9e0718f51..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); 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_rtp_asterisk.c b/res/res_rtp_asterisk.c index c88903777..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, @@ -5145,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; @@ -5158,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; } } @@ -5171,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; @@ -5203,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 */ @@ -5243,6 +5351,7 @@ static const char *rtcp_payload_type2str(unsigned int pt) #define RTCP_RR_BLOCK_WORD_LENGTH 6 #define RTCP_HEADER_SSRC_LENGTH 2 #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) { @@ -5319,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); @@ -5341,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: @@ -5392,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: @@ -5417,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. @@ -5438,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 @@ -5581,6 +5714,24 @@ 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 */ rc = AST_RTP_RTCP_FMT_FIR; @@ -5981,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; @@ -6614,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 |