|
|
|
|
@ -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);
|
|
|
|
|
|
|
|
|
|
@ -1158,6 +1171,39 @@ static int softmix_bridge_write_control(struct ast_bridge *bridge, struct ast_br
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*!
|
|
|
|
|
* \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.
|
|
|
|
|
@ -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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -1823,6 +1993,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
|
|
|
|
|
*
|
|
|
|
|
@ -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().
|
|
|
|
|
|