summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES31
-rw-r--r--apps/app_echo.c3
-rwxr-xr-xconfigure65
-rw-r--r--configure.ac6
-rw-r--r--doc/appdocsxml.xslt20
-rw-r--r--include/asterisk/autoconfig.h.in3
-rw-r--r--include/asterisk/bridge.h9
-rw-r--r--include/asterisk/channel.h49
-rw-r--r--include/asterisk/stasis_bridges.h4
-rw-r--r--main/asterisk.c21
-rw-r--r--main/autoservice.c66
-rw-r--r--main/bridge.c22
-rw-r--r--main/channel.c107
-rw-r--r--main/channel_internal_api.c6
-rw-r--r--main/codec_builtin.c16
-rw-r--r--main/file.c65
-rw-r--r--main/manager_bridges.c52
-rw-r--r--main/stasis_bridges.c29
-rw-r--r--res/ari/ari_model_validators.c121
-rw-r--r--res/ari/ari_model_validators.h27
-rw-r--r--res/ari/resource_bridges.c66
-rw-r--r--res/ari/resource_bridges.h28
-rw-r--r--res/res_agi.c38
-rw-r--r--res/res_ari_bridges.c146
-rw-r--r--res/res_format_attr_opus.c10
-rw-r--r--res/stasis/app.c7
-rw-r--r--rest-api/api-docs/bridges.json82
-rw-r--r--rest-api/api-docs/events.json15
-rw-r--r--tests/test_file.c14
29 files changed, 942 insertions, 186 deletions
diff --git a/CHANGES b/CHANGES
index b9d19a6f2..e7d6cca93 100644
--- a/CHANGES
+++ b/CHANGES
@@ -62,6 +62,37 @@ RTP
--- Functionality changes from Asterisk 14.1.0 to Asterisk 14.2.0 ----------
------------------------------------------------------------------------------
+AMI
+------------------
+ * Events that reference a bridge may now contain two new optional fields:
+ - 'BridgeVideoSourceMode': the video source mode for the bridge.
+ Can be one of 'none', 'talker', or 'single'.
+ - 'BridgeVideoSource': the unique ID of the channel that is the video
+ source in this bridge, if one exists.
+
+ * A new event, BridgeVideoSourceUpdate, has been added with a class
+ authorization of CALL. The event is raised when the video source changes
+ in a multi-party mixing bridge.
+
+ARI
+------------------
+ * The bridges resource now exposes two new operations:
+ - POST /bridges/{bridgeId}/videoSource/{channelId}: Set a video source in a
+ multi-party mixing bridge
+ - DELETE /bridges/{bridgeId}/videoSource: Remove the set video source,
+ reverting to talk detection for the video source
+
+ * The bridge model in any returned response or event now contains the following
+ optional fields:
+ - video_mode: the video source mode for the bridge. Can be one of 'none',
+ 'talker', or 'single'.
+ - video_source_id: the unique ID of the channel that is the video source
+ in this bridge, if one exists.
+
+ * A new event, BridgeVideoSourceChanged, has been added for bridges.
+ Applications subscribed to a bridge will receive this event when the source
+ of video changes in a mixing bridge.
+
res_pjsip
------------------
* Automatic dual stack support is now implemented. Depending on DNS resolution
diff --git a/apps/app_echo.c b/apps/app_echo.c
index 0f4c94b8d..e50173af8 100644
--- a/apps/app_echo.c
+++ b/apps/app_echo.c
@@ -66,7 +66,8 @@ static int echo_exec(struct ast_channel *chan, const char *data)
f->delivery.tv_sec = 0;
f->delivery.tv_usec = 0;
if (f->frametype == AST_FRAME_CONTROL
- && f->subclass.integer == AST_CONTROL_VIDUPDATE) {
+ && f->subclass.integer == AST_CONTROL_VIDUPDATE
+ && !fir_sent) {
if (ast_write(chan, f) < 0) {
ast_frfree(f);
goto end;
diff --git a/configure b/configure
index e90b35287..b3661c0fc 100755
--- a/configure
+++ b/configure
@@ -1059,6 +1059,10 @@ PBX_LIBXML2
LIBXML2_DIR
LIBXML2_INCLUDE
LIBXML2_LIB
+PBX_LIBEDIT_IS_UNICODE
+LIBEDIT_IS_UNICODE_DIR
+LIBEDIT_IS_UNICODE_INCLUDE
+LIBEDIT_IS_UNICODE_LIB
PBX_LIBEDIT
LIBEDIT_DIR
LIBEDIT_INCLUDE
@@ -10740,6 +10744,18 @@ fi
+LIBEDIT_IS_UNICODE_DESCRIP="Libedit compiled for unicode"
+LIBEDIT_IS_UNICODE_OPTION=libedit
+LIBEDIT_IS_UNICODE_DIR=${LIBEDIT_DIR}
+
+PBX_LIBEDIT_IS_UNICODE=0
+
+
+
+
+
+
+
LIBXML2_DESCRIP="LibXML2"
LIBXML2_OPTION="libxml2"
PBX_LIBXML2=0
@@ -21148,11 +21164,58 @@ fi
fi
if test "$PBX_LIBEDIT" = "1"; then
- LIBEDIT_INTERNAL="no"
+ LIBEDIT_INTERNAL="no"
fi
fi
if test "${LIBEDIT_INTERNAL}" = "yes"; then
PBX_LIBEDIT=1
+ LIBEDIT_IS_UNICODE=no
+ else
+
+ if test "x${PBX_LIBEDIT_IS_UNICODE}" != "x1" -a "${USE_LIBEDIT_IS_UNICODE}" != "no"; then
+ if test "xTesting for libedit unicode support" != "x"; then
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for Testing for libedit unicode support" >&5
+$as_echo_n "checking for Testing for libedit unicode support... " >&6; }
+ else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking if \"el_rfunc_t *callback;\" compiles using histedit.h" >&5
+$as_echo_n "checking if \"el_rfunc_t *callback;\" compiles using histedit.h... " >&6; }
+ fi
+ saved_cppflags="${CPPFLAGS}"
+ if test "x${LIBEDIT_IS_UNICODE_DIR}" != "x"; then
+ LIBEDIT_IS_UNICODE_INCLUDE="-I${LIBEDIT_IS_UNICODE_DIR}/include"
+ fi
+ CPPFLAGS="${CPPFLAGS} ${LIBEDIT_IS_UNICODE_INCLUDE}"
+
+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h. */
+ #include <histedit.h>
+int
+main ()
+{
+ el_rfunc_t *callback;;
+
+ ;
+ return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5
+$as_echo "yes" >&6; }
+ PBX_LIBEDIT_IS_UNICODE=1
+
+$as_echo "#define HAVE_LIBEDIT_IS_UNICODE 1" >>confdefs.h
+
+
+
+else
+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+ CPPFLAGS="${saved_cppflags}"
+ fi
+
fi
fi
diff --git a/configure.ac b/configure.ac
index db21fb3a6..d103ecae0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -479,6 +479,7 @@ AST_EXT_LIB_SETUP([KQUEUE], [kqueue support], [kqueue])
AST_EXT_LIB_SETUP([LDAP], [OpenLDAP], [ldap])
AST_LIBCURL_CHECK_CONFIG([], [7.10.1])
AST_EXT_LIB_SETUP([LIBEDIT], [NetBSD Editline library], [libedit], [, use 'internal' Editline otherwise])
+AST_EXT_LIB_SETUP_OPTIONAL([LIBEDIT_IS_UNICODE], [Libedit compiled for unicode], [LIBEDIT], [libedit])
AST_EXT_LIB_SETUP([LIBXML2], [LibXML2], [libxml2])
AST_EXT_LIB_SETUP([LIBXSLT], [LibXSLT], [libxslt])
AST_EXT_LIB_SETUP_OPTIONAL([LIBXSLT_CLEANUP], [LibXSLT Library Cleanup Function], [LIBXSLT], [libxslt])
@@ -1531,11 +1532,14 @@ if test "${USE_LIBEDIT}" != "no"; then
if test "${LIBEDIT_SYSTEM}" = "yes"; then
AST_PKG_CONFIG_CHECK(LIBEDIT, libedit)
if test "$PBX_LIBEDIT" = "1"; then
- LIBEDIT_INTERNAL="no"
+ LIBEDIT_INTERNAL="no"
fi
fi
if test "${LIBEDIT_INTERNAL}" = "yes"; then
PBX_LIBEDIT=1
+ LIBEDIT_IS_UNICODE=no
+ else
+ AST_C_COMPILE_CHECK([LIBEDIT_IS_UNICODE], [el_rfunc_t *callback;], [histedit.h], [], [Testing for libedit unicode support])
fi
fi
diff --git a/doc/appdocsxml.xslt b/doc/appdocsxml.xslt
index f067decae..511011ae8 100644
--- a/doc/appdocsxml.xslt
+++ b/doc/appdocsxml.xslt
@@ -145,5 +145,25 @@
</xsl:attribute>
<para>Number of channels in the bridge</para>
</xsl:element>
+ <xsl:element name="parameter">
+ <xsl:attribute name="name">
+ <xsl:value-of select="concat(@prefix, 'BridgeVideoSourceMode')" />
+ </xsl:attribute>
+ <enumlist>
+ <enum name="none"/>
+ <enum name="talker"/>
+ <enum name="single"/>
+ </enumlist>
+ <para>The video source mode for the bridge.</para>
+ </xsl:element>
+ <xsl:element name="parameter">
+ <xsl:attribute name="required">
+ false
+ </xsl:attribute>
+ <xsl:attribute name="name">
+ <xsl:value-of select="concat(@prefix, 'BridgeVideoSource')" />
+ </xsl:attribute>
+ <para>If there is a video source for the bridge, the unique ID of the channel that is the video source.</para>
+ </xsl:element>
</xsl:template>
</xsl:stylesheet>
diff --git a/include/asterisk/autoconfig.h.in b/include/asterisk/autoconfig.h.in
index bac2ecf48..b50484ad6 100644
--- a/include/asterisk/autoconfig.h.in
+++ b/include/asterisk/autoconfig.h.in
@@ -402,6 +402,9 @@
/* Define if your system has the LIBEDIT libraries. */
#undef HAVE_LIBEDIT
+/* Define if your system has the LIBEDIT_IS_UNICODE headers. */
+#undef HAVE_LIBEDIT_IS_UNICODE
+
/* Define to 1 if you have the <libintl.h> header file. */
#undef HAVE_LIBINTL_H
diff --git a/include/asterisk/bridge.h b/include/asterisk/bridge.h
index acea2f01f..61cecbdd6 100644
--- a/include/asterisk/bridge.h
+++ b/include/asterisk/bridge.h
@@ -903,6 +903,15 @@ int ast_bridge_is_video_src(struct ast_bridge *bridge, struct ast_channel *chan)
*/
void ast_bridge_remove_video_src(struct ast_bridge *bridge, struct ast_channel *chan);
+/*!
+ * \brief Converts an enum representation of a bridge video mode to string
+ *
+ * \param video_mode The video mode
+ *
+ * \retval A string representation of \c video_mode
+ */
+const char *ast_bridge_video_mode_to_string(enum ast_bridge_video_mode_type video_mode);
+
enum ast_transfer_result {
/*! The transfer completed successfully */
AST_BRIDGE_TRANSFER_SUCCESS,
diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h
index cfd8384cb..5c73c777e 100644
--- a/include/asterisk/channel.h
+++ b/include/asterisk/channel.h
@@ -967,6 +967,16 @@ enum {
* The channel is executing a subroutine or macro
*/
AST_FLAG_SUBROUTINE_EXEC = (1 << 27),
+ /*!
+ * The channel is currently in an operation where
+ * frames should be deferred.
+ */
+ AST_FLAG_DEFER_FRAMES = (1 << 28),
+ /*!
+ * The channel is currently deferring hangup frames
+ * in addition to other frame types.
+ */
+ AST_FLAG_DEFER_HANGUP_FRAMES = (1 << 29),
};
/*! \brief ast_bridge_config flags */
@@ -4701,4 +4711,43 @@ enum ast_channel_error {
*/
enum ast_channel_error ast_channel_errno(void);
+/*!
+ * \brief Retrieve the deferred read queue.
+ */
+struct ast_readq_list *ast_channel_deferred_readq(struct ast_channel *chan);
+
+/*!
+ * \brief Start deferring deferrable frames on this channel
+ *
+ * Sometimes, a channel gets entered into a mode where a "main" application
+ * is tasked with servicing frames on the channel, but that application does
+ * not need to act on those frames. However, it would be imprudent to simply
+ * drop important frames. This function can be called so that important frames
+ * will be deferred, rather than placed in the channel frame queue as normal.
+ *
+ * Hangups are an interesting frame type. Hangups will always be detectable by
+ * a reader when a channel is deferring frames. If the defer_hangups parameter
+ * is non-zero, then the hangup frame will also be duplicated and deferred, so
+ * that the next reader of the channel will get the hangup frame, too.
+ *
+ * \pre chan MUST be locked before calling
+ *
+ * \param chan The channel on which frames should be deferred
+ * \param defer_hangups Defer hangups in addition to other deferrable frames
+ */
+void ast_channel_start_defer_frames(struct ast_channel *chan, int defer_hangups);
+
+/*!
+ * \brief Stop deferring deferrable frames on this channel
+ *
+ * When it is time to stop deferring frames on the channel, all deferred frames
+ * will be queued onto the channel's read queue so that the next servicer of
+ * the channel can handle those frames as necessary.
+ *
+ * \pre chan MUST be locked before calling
+ *
+ * \param chan The channel on which to stop deferring frames.
+ */
+void ast_channel_stop_defer_frames(struct ast_channel *chan);
+
#endif /* _ASTERISK_CHANNEL_H */
diff --git a/include/asterisk/stasis_bridges.h b/include/asterisk/stasis_bridges.h
index d549e4620..05d356cc2 100644
--- a/include/asterisk/stasis_bridges.h
+++ b/include/asterisk/stasis_bridges.h
@@ -58,6 +58,10 @@ struct ast_bridge_snapshot {
unsigned int num_channels;
/*! Number of active channels in the bridge. */
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/main/asterisk.c b/main/asterisk.c
index be6c7cc32..4a6567f73 100644
--- a/main/asterisk.c
+++ b/main/asterisk.c
@@ -2691,7 +2691,11 @@ static void send_rasterisk_connect_commands(void)
}
}
+#ifdef HAVE_LIBEDIT_IS_UNICODE
+static int ast_el_read_char(EditLine *editline, wchar_t *cp)
+#else
static int ast_el_read_char(EditLine *editline, char *cp)
+#endif
{
int num_read = 0;
int lastpos = 0;
@@ -2721,10 +2725,16 @@ static int ast_el_read_char(EditLine *editline, char *cp)
}
if (!ast_opt_exec && fds[1].revents) {
- num_read = read(STDIN_FILENO, cp, 1);
+ char c = '\0';
+ num_read = read(STDIN_FILENO, &c, 1);
if (num_read < 1) {
break;
} else {
+#ifdef HAVE_LIBEDIT_IS_UNICODE
+ *cp = btowc(c);
+#else
+ *cp = c;
+#endif
return (num_read);
}
}
@@ -2768,7 +2778,11 @@ static int ast_el_read_char(EditLine *editline, char *cp)
console_print(buf);
if ((res < EL_BUF_SIZE - 1) && ((buf[res-1] == '\n') || (res >= 2 && buf[res-2] == '\n'))) {
+#ifdef HAVE_LIBEDIT_IS_UNICODE
+ *cp = btowc(CC_REFRESH);
+#else
*cp = CC_REFRESH;
+#endif
return(1);
} else {
lastpos = 1;
@@ -2776,7 +2790,12 @@ static int ast_el_read_char(EditLine *editline, char *cp)
}
}
+#ifdef HAVE_LIBEDIT_IS_UNICODE
+ *cp = btowc('\0');
+#else
*cp = '\0';
+#endif
+
return (0);
}
diff --git a/main/autoservice.c b/main/autoservice.c
index 11c9eab96..c3f24276c 100644
--- a/main/autoservice.c
+++ b/main/autoservice.c
@@ -59,10 +59,6 @@ struct asent {
unsigned int use_count;
unsigned int orig_end_dtmf_flag:1;
unsigned int ignore_frame_types;
- /*! Frames go on at the head of deferred_frames, so we have the frames
- * from newest to oldest. As we put them at the head of the readq, we'll
- * end up with them in the right order for the channel's readq. */
- AST_LIST_HEAD_NOLOCK(, ast_frame) deferred_frames;
AST_LIST_ENTRY(asent) list;
};
@@ -77,19 +73,13 @@ static int as_chan_list_state;
static void *autoservice_run(void *ign)
{
ast_callid callid = 0;
- struct ast_frame hangup_frame = {
- .frametype = AST_FRAME_CONTROL,
- .subclass.integer = AST_CONTROL_HANGUP,
- };
while (!asexit) {
struct ast_channel *mons[MAX_AUTOMONS];
- struct asent *ents[MAX_AUTOMONS];
struct ast_channel *chan;
struct asent *as;
- int i, x = 0, ms = 50;
+ int x = 0, ms = 50;
struct ast_frame *f = NULL;
- struct ast_frame *defer_frame = NULL;
AST_LIST_LOCK(&aslist);
@@ -104,7 +94,6 @@ static void *autoservice_run(void *ign)
AST_LIST_TRAVERSE(&aslist, as, list) {
if (!ast_check_hangup(as->chan)) {
if (x < MAX_AUTOMONS) {
- ents[x] = as;
mons[x++] = as->chan;
} else {
ast_log(LOG_WARNING, "Exceeded maximum number of automatic monitoring events. Fix autoservice.c\n");
@@ -132,51 +121,9 @@ static void *autoservice_run(void *ign)
ast_callid_threadassoc_change(callid);
f = ast_read(chan);
-
- if (!f) {
- /* No frame means the channel has been hung up.
- * A hangup frame needs to be queued here as ast_waitfor() may
- * never return again for the condition to be detected outside
- * of autoservice. So, we'll leave a HANGUP queued up so the
- * thread in charge of this channel will know. */
-
- defer_frame = &hangup_frame;
- } else if (ast_is_deferrable_frame(f)) {
- defer_frame = f;
- } else {
- /* Can't defer. Discard and continue with next. */
+ if (f) {
ast_frfree(f);
- continue;
}
-
- for (i = 0; i < x; i++) {
- struct ast_frame *dup_f;
-
- if (mons[i] != chan) {
- continue;
- }
-
- if (!f) { /* defer_frame == &hangup_frame */
- if ((dup_f = ast_frdup(defer_frame))) {
- AST_LIST_INSERT_HEAD(&ents[i]->deferred_frames, dup_f, frame_list);
- }
- } else {
- if ((dup_f = ast_frisolate(defer_frame))) {
- AST_LIST_INSERT_HEAD(&ents[i]->deferred_frames, dup_f, frame_list);
- }
- if (dup_f != defer_frame) {
- ast_frfree(defer_frame);
- }
- }
-
- break;
- }
- /* The ast_waitfor_n() call will only read frames from
- * the channels' file descriptors. If ast_waitfor_n()
- * returns non-NULL, then one of the channels in the
- * mons array must have triggered the return. It's
- * therefore impossible that we got here while (i >= x).
- * If we did, we'd need to ast_frfree(f) if (f). */
}
ast_callid_threadassoc_change(0);
@@ -215,6 +162,7 @@ int ast_autoservice_start(struct ast_channel *chan)
as->orig_end_dtmf_flag = ast_test_flag(ast_channel_flags(chan), AST_FLAG_END_DTMF_ONLY) ? 1 : 0;
if (!as->orig_end_dtmf_flag)
ast_set_flag(ast_channel_flags(chan), AST_FLAG_END_DTMF_ONLY);
+ ast_channel_start_defer_frames(chan, 1);
ast_channel_unlock(chan);
AST_LIST_LOCK(&aslist);
@@ -248,7 +196,6 @@ int ast_autoservice_stop(struct ast_channel *chan)
{
int res = -1;
struct asent *as, *removed = NULL;
- struct ast_frame *f;
int chan_list_state;
AST_LIST_LOCK(&aslist);
@@ -300,12 +247,7 @@ int ast_autoservice_stop(struct ast_channel *chan)
}
ast_channel_lock(chan);
- while ((f = AST_LIST_REMOVE_HEAD(&as->deferred_frames, frame_list))) {
- if (!((1 << f->frametype) & as->ignore_frame_types)) {
- ast_queue_frame_head(chan, f);
- }
- ast_frfree(f);
- }
+ ast_channel_stop_defer_frames(chan);
ast_channel_unlock(chan);
ast_free(as);
diff --git a/main/bridge.c b/main/bridge.c
index 1bb60eb7a..13c01fa27 100644
--- a/main/bridge.c
+++ b/main/bridge.c
@@ -3774,8 +3774,7 @@ void ast_bridge_set_single_src_video_mode(struct ast_bridge *bridge, struct ast_
bridge->name, bridge->uniqueid,
ast_channel_name(video_src_chan),
ast_channel_uniqueid(video_src_chan));
- ast_test_suite_event_notify("BRIDGE_VIDEO_MODE", "Message: video mode set to single source\r\nVideo Mode: %u\r\nVideo Channel: %s",
- bridge->softmix.video_mode.mode, ast_channel_name(video_src_chan));
+ ast_bridge_publish_state(bridge);
ast_indicate(video_src_chan, AST_CONTROL_VIDUPDATE);
ast_bridge_unlock(bridge);
}
@@ -3785,8 +3784,6 @@ void ast_bridge_set_talker_src_video_mode(struct ast_bridge *bridge)
ast_bridge_lock(bridge);
cleanup_video_mode(bridge);
bridge->softmix.video_mode.mode = AST_BRIDGE_VIDEO_MODE_TALKER_SRC;
- ast_test_suite_event_notify("BRIDGE_VIDEO_MODE", "Message: video mode set to talker source\r\nVideo Mode: %u",
- bridge->softmix.video_mode.mode);
ast_bridge_unlock(bridge);
}
@@ -3818,7 +3815,7 @@ void ast_bridge_update_talker_src_video_mode(struct ast_bridge *bridge, struct a
bridge->name, bridge->uniqueid,
ast_channel_name(data->chan_vsrc),
ast_channel_uniqueid(data->chan_vsrc));
- ast_test_suite_event_notify("BRIDGE_VIDEO_SRC", "Message: video source updated\r\nVideo Channel: %s", ast_channel_name(data->chan_vsrc));
+ ast_bridge_publish_state(bridge);
ast_indicate(data->chan_vsrc, AST_CONTROL_VIDUPDATE);
} else if ((data->average_talking_energy < talker_energy) && !is_keyframe) {
ast_indicate(chan, AST_CONTROL_VIDUPDATE);
@@ -3829,7 +3826,7 @@ void ast_bridge_update_talker_src_video_mode(struct ast_bridge *bridge, struct a
bridge->name, bridge->uniqueid,
ast_channel_name(data->chan_vsrc),
ast_channel_uniqueid(data->chan_vsrc));
- ast_test_suite_event_notify("BRIDGE_VIDEO_SRC", "Message: video source updated\r\nVideo Channel: %s", ast_channel_name(data->chan_vsrc));
+ ast_bridge_publish_state(bridge);
ast_indicate(chan, AST_CONTROL_VIDUPDATE);
} else if (!data->chan_old_vsrc && is_keyframe) {
data->chan_old_vsrc = ast_channel_ref(chan);
@@ -3920,6 +3917,19 @@ void ast_bridge_remove_video_src(struct ast_bridge *bridge, struct ast_channel *
ast_bridge_unlock(bridge);
}
+const char *ast_bridge_video_mode_to_string(enum ast_bridge_video_mode_type video_mode)
+{
+ switch (video_mode) {
+ case AST_BRIDGE_VIDEO_MODE_TALKER_SRC:
+ return "talker";
+ case AST_BRIDGE_VIDEO_MODE_SINGLE_SRC:
+ return "single";
+ case AST_BRIDGE_VIDEO_MODE_NONE:
+ default:
+ return "none";
+ }
+}
+
static int channel_hash(const void *obj, int flags)
{
const struct ast_channel *chan = obj;
diff --git a/main/channel.c b/main/channel.c
index bdf918fab..bd5f35172 100644
--- a/main/channel.c
+++ b/main/channel.c
@@ -1062,6 +1062,26 @@ struct ast_channel *__ast_dummy_channel_alloc(const char *file, int line, const
return tmp;
}
+void ast_channel_start_defer_frames(struct ast_channel *chan, int defer_hangups)
+{
+ ast_set_flag(ast_channel_flags(chan), AST_FLAG_DEFER_FRAMES);
+ ast_set2_flag(ast_channel_flags(chan), defer_hangups, AST_FLAG_DEFER_HANGUP_FRAMES);
+}
+
+void ast_channel_stop_defer_frames(struct ast_channel *chan)
+{
+ ast_clear_flag(ast_channel_flags(chan), AST_FLAG_DEFER_FRAMES);
+
+ /* Move the deferred frames onto the channel read queue, ahead of other queued frames */
+ ast_queue_frame_head(chan, AST_LIST_FIRST(ast_channel_deferred_readq(chan)));
+ /* ast_frfree will mosey down the list and free them all */
+ if (!AST_LIST_EMPTY(ast_channel_deferred_readq(chan))) {
+ ast_frfree(AST_LIST_FIRST(ast_channel_deferred_readq(chan)));
+ }
+ /* Reset the list to be empty */
+ AST_LIST_HEAD_INIT_NOLOCK(ast_channel_deferred_readq(chan));
+}
+
static int __ast_queue_frame(struct ast_channel *chan, struct ast_frame *fin, int head, struct ast_frame *after)
{
struct ast_frame *f;
@@ -1525,19 +1545,18 @@ int ast_safe_sleep_conditional(struct ast_channel *chan, int timeout_ms, int (*c
int res = 0;
struct timeval start;
int ms;
- AST_LIST_HEAD_NOLOCK(, ast_frame) deferred_frames;
-
- AST_LIST_HEAD_INIT_NOLOCK(&deferred_frames);
/* If no other generator is present, start silencegen while waiting */
if (ast_opt_transmit_silence && !ast_channel_generatordata(chan)) {
silgen = ast_channel_start_silence_generator(chan);
}
+ ast_channel_lock(chan);
+ ast_channel_start_defer_frames(chan, 0);
+ ast_channel_unlock(chan);
+
start = ast_tvnow();
while ((ms = ast_remaining_ms(start, timeout_ms))) {
- struct ast_frame *dup_f = NULL;
-
if (cond && ((*cond)(data) == 0)) {
break;
}
@@ -1552,18 +1571,7 @@ int ast_safe_sleep_conditional(struct ast_channel *chan, int timeout_ms, int (*c
res = -1;
break;
}
-
- if (!ast_is_deferrable_frame(f)) {
- ast_frfree(f);
- continue;
- }
-
- if ((dup_f = ast_frisolate(f))) {
- if (dup_f != f) {
- ast_frfree(f);
- }
- AST_LIST_INSERT_HEAD(&deferred_frames, dup_f, frame_list);
- }
+ ast_frfree(f);
}
}
@@ -1572,17 +1580,8 @@ int ast_safe_sleep_conditional(struct ast_channel *chan, int timeout_ms, int (*c
ast_channel_stop_silence_generator(chan, silgen);
}
- /* We need to free all the deferred frames, but we only need to
- * queue the deferred frames if there was no error and no
- * hangup was received
- */
ast_channel_lock(chan);
- while ((f = AST_LIST_REMOVE_HEAD(&deferred_frames, frame_list))) {
- if (!res) {
- ast_queue_frame_head(chan, f);
- }
- ast_frfree(f);
- }
+ ast_channel_stop_defer_frames(chan);
ast_channel_unlock(chan);
return res;
@@ -3883,6 +3882,36 @@ static struct ast_frame *__ast_read(struct ast_channel *chan, int dropaudio)
if (!AST_LIST_EMPTY(ast_channel_readq(chan))) {
int skip_dtmf = should_skip_dtmf(chan);
+ if (ast_test_flag(ast_channel_flags(chan), AST_FLAG_DEFER_FRAMES)) {
+ AST_LIST_TRAVERSE_SAFE_BEGIN(ast_channel_readq(chan), f, frame_list) {
+ if (ast_is_deferrable_frame(f)) {
+ if(f->frametype == AST_FRAME_CONTROL &&
+ (f->subclass.integer == AST_CONTROL_HANGUP ||
+ f->subclass.integer == AST_CONTROL_END_OF_Q)) {
+ /* Hangup is a special case. We want to defer the frame, but we also do not
+ * want to remove it from the frame queue. So rather than just moving the frame
+ * over, we duplicate it and move the copy to the deferred readq.
+ *
+ * The reason for this? This way, whoever calls ast_read() will get a NULL return
+ * immediately and can tell the channel has hung up and do what it needs to. Also,
+ * when frame deferral finishes, then whoever calls ast_read() next will also get
+ * the hangup.
+ */
+ if (ast_test_flag(ast_channel_flags(chan), AST_FLAG_DEFER_HANGUP_FRAMES)) {
+ struct ast_frame *dup;
+
+ dup = ast_frdup(f);
+ AST_LIST_INSERT_TAIL(ast_channel_deferred_readq(chan), dup, frame_list);
+ }
+ } else {
+ AST_LIST_INSERT_TAIL(ast_channel_deferred_readq(chan), f, frame_list);
+ AST_LIST_REMOVE_CURRENT(frame_list);
+ }
+ }
+ }
+ AST_LIST_TRAVERSE_SAFE_END;
+ }
+
AST_LIST_TRAVERSE_SAFE_BEGIN(ast_channel_readq(chan), f, frame_list) {
/* We have to be picky about which frame we pull off of the readq because
* there are cases where we want to leave DTMF frames on the queue until
@@ -10278,9 +10307,15 @@ int ast_channel_connected_line_macro(struct ast_channel *autoservice_chan, struc
ast_party_connected_line_copy(ast_channel_connected(macro_chan), connected);
}
+ ast_channel_start_defer_frames(macro_chan, 0);
ast_channel_unlock(macro_chan);
retval = ast_app_run_macro(autoservice_chan, macro_chan, macro, macro_args);
+
+ ast_channel_lock(macro_chan);
+ ast_channel_stop_defer_frames(macro_chan);
+ ast_channel_unlock(macro_chan);
+
if (!retval) {
struct ast_party_connected_line saved_connected;
@@ -10328,9 +10363,15 @@ int ast_channel_redirecting_macro(struct ast_channel *autoservice_chan, struct a
ast_party_redirecting_copy(ast_channel_redirecting(macro_chan), redirecting);
}
+ ast_channel_start_defer_frames(macro_chan, 0);
ast_channel_unlock(macro_chan);
retval = ast_app_run_macro(autoservice_chan, macro_chan, macro, macro_args);
+
+ ast_channel_lock(macro_chan);
+ ast_channel_stop_defer_frames(macro_chan);
+ ast_channel_unlock(macro_chan);
+
if (!retval) {
struct ast_party_redirecting saved_redirecting;
@@ -10371,9 +10412,15 @@ int ast_channel_connected_line_sub(struct ast_channel *autoservice_chan, struct
ast_party_connected_line_copy(ast_channel_connected(sub_chan), connected);
}
+ ast_channel_start_defer_frames(sub_chan, 0);
ast_channel_unlock(sub_chan);
retval = ast_app_run_sub(autoservice_chan, sub_chan, sub, sub_args, 0);
+
+ ast_channel_lock(sub_chan);
+ ast_channel_stop_defer_frames(sub_chan);
+ ast_channel_unlock(sub_chan);
+
if (!retval) {
struct ast_party_connected_line saved_connected;
@@ -10414,9 +10461,15 @@ int ast_channel_redirecting_sub(struct ast_channel *autoservice_chan, struct ast
ast_party_redirecting_copy(ast_channel_redirecting(sub_chan), redirecting);
}
+ ast_channel_start_defer_frames(sub_chan, 0);
ast_channel_unlock(sub_chan);
retval = ast_app_run_sub(autoservice_chan, sub_chan, sub, sub_args, 0);
+
+ ast_channel_lock(sub_chan);
+ ast_channel_stop_defer_frames(sub_chan);
+ ast_channel_unlock(sub_chan);
+
if (!retval) {
struct ast_party_redirecting saved_redirecting;
diff --git a/main/channel_internal_api.c b/main/channel_internal_api.c
index 1cb91e7c3..50f6c5da9 100644
--- a/main/channel_internal_api.c
+++ b/main/channel_internal_api.c
@@ -221,6 +221,7 @@ struct ast_channel {
struct stasis_cp_single *topics; /*!< Topic for all channel's events */
struct stasis_forward *endpoint_forward; /*!< Subscription for event forwarding to endpoint's topic */
struct stasis_forward *endpoint_cache_forward; /*!< Subscription for cache updates to endpoint's topic */
+ struct ast_readq_list deferred_readq;
};
/*! \brief The monotonically increasing integer counter for channel uniqueids */
@@ -1681,3 +1682,8 @@ enum ast_channel_error ast_channel_internal_errno(void)
return *error_code;
}
+
+struct ast_readq_list *ast_channel_deferred_readq(struct ast_channel *chan)
+{
+ return &chan->deferred_readq;
+}
diff --git a/main/codec_builtin.c b/main/codec_builtin.c
index cc4edd239..f622c9105 100644
--- a/main/codec_builtin.c
+++ b/main/codec_builtin.c
@@ -729,6 +729,21 @@ static struct ast_codec g719 = {
.get_length = g719_length,
};
+static int opus_samples(struct ast_frame *frame)
+{
+ /*
+ * XXX This is likely not at all what's intended from this
+ * callback. If you have codec_opus.so loaded then this
+ * function is overridden anyway. However, since opus is
+ * variable bit rate and I cannot extract the calculation code
+ * from the opus library, I am going to punt and assume 20ms
+ * worth of samples. In testing, this has worked just fine.
+ * Pass through support doesn't seem to care about the value
+ * returned anyway.
+ */
+ return ast_format_get_sample_rate(frame->subclass.format) / 50;
+}
+
static struct ast_codec opus = {
.name = "opus",
.description = "Opus Codec",
@@ -737,6 +752,7 @@ static struct ast_codec opus = {
.minimum_ms = 20,
.maximum_ms = 60,
.default_ms = 20,
+ .samples_count = opus_samples,
.minimum_bytes = 10,
};
diff --git a/main/file.c b/main/file.c
index 37b9e7911..fb4ede6c8 100644
--- a/main/file.c
+++ b/main/file.c
@@ -1093,27 +1093,27 @@ int ast_filecopy(const char *filename, const char *filename2, const char *fmt)
return filehelper(filename, filename2, fmt, ACTION_COPY);
}
-static int __ast_file_read_dirs(struct ast_str **path, ast_file_on_file on_file,
+static int __ast_file_read_dirs(const char *path, ast_file_on_file on_file,
void *obj, int max_depth)
{
DIR *dir;
struct dirent *entry;
- size_t size;
int res;
- if (!(dir = opendir(ast_str_buffer(*path)))) {
+ if (!(dir = opendir(path))) {
ast_log(LOG_ERROR, "Error opening directory - %s: %s\n",
- ast_str_buffer(*path), strerror(errno));
+ path, strerror(errno));
return -1;
}
- size = ast_str_strlen(*path);
--max_depth;
res = 0;
while ((entry = readdir(dir)) != NULL && !errno) {
- int is_file, is_dir, used_stat = 0;
+ int is_file = 0;
+ int is_dir = 0;
+ RAII_VAR(char *, full_path, NULL, ast_free);
if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) {
continue;
@@ -1128,23 +1128,24 @@ static int __ast_file_read_dirs(struct ast_str **path, ast_file_on_file on_file,
if (entry->d_type != DT_UNKNOWN && entry->d_type != DT_LNK) {
is_file = entry->d_type == DT_REG;
is_dir = entry->d_type == DT_DIR;
- ast_log(LOG_VERBOSE, "!###### d_name=%s, path=%s, NO USE STAT used_stat=%d\n", entry->d_name, ast_str_buffer(*path), used_stat);
} else
#endif
{
struct stat statbuf;
/*
- * If using the stat function the file needs to be appended to the
- * path so it can be found. However, before appending make sure the
- * path contains only the directory for this depth level.
+ * Don't use alloca or we risk blowing out the stack if recursing
+ * into subdirectories.
*/
- ast_str_truncate(*path, size);
- ast_str_append(path, 0, "/%s", entry->d_name);
+ full_path = ast_malloc(strlen(path) + strlen(entry->d_name) + 2);
+ if (!full_path) {
+ return -1;
+ }
+ sprintf(full_path, "%s/%s", path, entry->d_name);
- if (stat(ast_str_buffer(*path), &statbuf)) {
+ if (stat(full_path, &statbuf)) {
ast_log(LOG_ERROR, "Error reading path stats - %s: %s\n",
- ast_str_buffer(*path), strerror(errno));
+ full_path, strerror(errno));
/*
* Output an error, but keep going. It could just be
* a broken link and other files could be fine.
@@ -1154,13 +1155,11 @@ static int __ast_file_read_dirs(struct ast_str **path, ast_file_on_file on_file,
is_file = S_ISREG(statbuf.st_mode);
is_dir = S_ISDIR(statbuf.st_mode);
- used_stat = 1;
- ast_log(LOG_VERBOSE, "!###### d_name=%s, path=%s, WE USED IT YO used_stat=%d\n", entry->d_name, ast_str_buffer(*path), used_stat);
}
if (is_file) {
/* If the handler returns non-zero then stop */
- if ((res = on_file(ast_str_buffer(*path), entry->d_name, obj))) {
+ if ((res = on_file(path, entry->d_name, obj))) {
break;
}
/* Otherwise move on to next item in directory */
@@ -1168,25 +1167,22 @@ static int __ast_file_read_dirs(struct ast_str **path, ast_file_on_file on_file,
}
if (!is_dir) {
- ast_debug(5, "Skipping %s: not a regular file or directory\n",
- ast_str_buffer(*path));
+ ast_debug(5, "Skipping %s: not a regular file or directory\n", full_path);
continue;
}
/* Only re-curse into sub-directories if not at the max depth */
if (max_depth != 0) {
- /*
- * If the stat function was used then the sub-directory has
- * already been appended, otherwise append it.
- */
- ast_log(LOG_VERBOSE, "!###### do dir d_name=%s, path=%s, used_stat=%d\n", entry->d_name, ast_str_buffer(*path), used_stat);
- if (!used_stat) {
- ast_str_truncate(*path, size);
- ast_str_append(path, 0, "/%s", entry->d_name);
- ast_log(LOG_VERBOSE, "!###### d_name=%s, path=%s\n", entry->d_name, ast_str_buffer(*path));
+ if (!full_path) {
+ /* Don't use alloca. See note above. */
+ full_path = ast_malloc(strlen(path) + strlen(entry->d_name) + 2);
+ if (!full_path) {
+ return -1;
+ }
+ sprintf(full_path, "%s/%s", path, entry->d_name);
}
- if ((res = __ast_file_read_dirs(path, on_file, obj, max_depth))) {
+ if ((res = __ast_file_read_dirs(full_path, on_file, obj, max_depth))) {
break;
}
}
@@ -1196,7 +1192,7 @@ static int __ast_file_read_dirs(struct ast_str **path, ast_file_on_file on_file,
if (!res && errno) {
ast_log(LOG_ERROR, "Error while reading directories - %s: %s\n",
- ast_str_buffer(*path), strerror(errno));
+ path, strerror(errno));
res = -1;
}
@@ -1217,27 +1213,20 @@ AST_MUTEX_DEFINE_STATIC(read_dirs_lock);
int ast_file_read_dirs(const char *dir_name, ast_file_on_file on_file, void *obj, int max_depth)
{
- struct ast_str *path;
int res;
- if (!(path = ast_str_create(256))) {
- return -1;
- }
-
- ast_str_set(&path, 0, "%s", dir_name);
errno = 0;
#if !defined(__GLIBC__)
ast_mutex_lock(&read_dirs_lock);
#endif
- res = __ast_file_read_dirs(&path, on_file, obj, max_depth);
+ res = __ast_file_read_dirs(dir_name, on_file, obj, max_depth);
#if !defined(__GLIBC__)
ast_mutex_unlock(&read_dirs_lock);
#endif
- ast_free(path);
return res;
}
diff --git a/main/manager_bridges.c b/main/manager_bridges.c
index c6e997f42..b7059f40c 100644
--- a/main/manager_bridges.c
+++ b/main/manager_bridges.c
@@ -91,6 +91,21 @@ static struct stasis_message_router *bridge_state_router;
</see-also>
</managerEventInstance>
</managerEvent>
+ <managerEvent language="en_US" name="BridgeVideoSourceUpdate">
+ <managerEventInstance class="EVENT_FLAG_CALL">
+ <synopsis>Raised when the channel that is the source of video in a bridge changes.</synopsis>
+ <syntax>
+ <bridge_snapshot/>
+ <parameter name="BridgePreviousVideoSource">
+ <para>The unique ID of the channel that was the video source.</para>
+ </parameter>
+ </syntax>
+ <see-also>
+ <ref type="managerEvent">BridgeCreate</ref>
+ <ref type="managerEvent">BridgeDestroy</ref>
+ </see-also>
+ </managerEventInstance>
+ </managerEvent>
<manager name="BridgeList" language="en_US">
<synopsis>
Get a list of bridges in the system.
@@ -222,18 +237,30 @@ struct ast_str *ast_manager_build_bridge_state_string_prefix(
"%sBridgeTechnology: %s\r\n"
"%sBridgeCreator: %s\r\n"
"%sBridgeName: %s\r\n"
- "%sBridgeNumChannels: %u\r\n",
+ "%sBridgeNumChannels: %u\r\n"
+ "%sBridgeVideoSourceMode: %s\r\n",
prefix, snapshot->uniqueid,
prefix, snapshot->subclass,
prefix, snapshot->technology,
prefix, ast_strlen_zero(snapshot->creator) ? "<unknown>": snapshot->creator,
prefix, ast_strlen_zero(snapshot->name) ? "<unknown>": snapshot->name,
- prefix, snapshot->num_channels);
+ prefix, snapshot->num_channels,
+ prefix, ast_bridge_video_mode_to_string(snapshot->video_mode));
if (!res) {
ast_free(out);
return NULL;
}
+ if (snapshot->video_mode != AST_BRIDGE_VIDEO_MODE_NONE
+ && !ast_strlen_zero(snapshot->video_source_id)) {
+ res = ast_str_append(&out, 0, "%sBridgeVideoSource: %s\r\n",
+ prefix, snapshot->video_source_id);
+ if (!res) {
+ ast_free(out);
+ return NULL;
+ }
+ }
+
return out;
}
@@ -261,6 +288,25 @@ static struct ast_manager_event_blob *bridge_create(
EVENT_FLAG_CALL, "BridgeCreate", NO_EXTRA_FIELDS);
}
+/* \brief Handle video source updates */
+static struct ast_manager_event_blob *bridge_video_update(
+ struct ast_bridge_snapshot *old_snapshot,
+ struct ast_bridge_snapshot *new_snapshot)
+{
+ if (!new_snapshot || !old_snapshot) {
+ return NULL;
+ }
+
+ if (!strcmp(old_snapshot->video_source_id, new_snapshot->video_source_id)) {
+ return NULL;
+ }
+
+ return ast_manager_event_blob_create(
+ EVENT_FLAG_CALL, "BridgeVideoSourceUpdate",
+ "BridgePreviousVideoSource: %s\r\n",
+ old_snapshot->video_source_id);
+}
+
/*! \brief Handle bridge destruction */
static struct ast_manager_event_blob *bridge_destroy(
struct ast_bridge_snapshot *old_snapshot,
@@ -274,9 +320,9 @@ static struct ast_manager_event_blob *bridge_destroy(
EVENT_FLAG_CALL, "BridgeDestroy", NO_EXTRA_FIELDS);
}
-
bridge_snapshot_monitor bridge_monitors[] = {
bridge_create,
+ bridge_video_update,
bridge_destroy,
};
diff --git a/main/stasis_bridges.c b/main/stasis_bridges.c
index 43722b90b..7f53bfe2d 100644
--- a/main/stasis_bridges.c
+++ b/main/stasis_bridges.c
@@ -242,7 +242,13 @@ struct ast_bridge_snapshot *ast_bridge_snapshot_create(struct ast_bridge *bridge
snapshot = ao2_alloc_options(sizeof(*snapshot), bridge_snapshot_dtor,
AO2_ALLOC_OPT_LOCK_NOLOCK);
- if (!snapshot || ast_string_field_init(snapshot, 128)) {
+ if (!snapshot) {
+ return NULL;
+ }
+
+ if (ast_string_field_init(snapshot, 128)
+ || ast_string_field_init_extended(snapshot, video_source_id)) {
+ ao2_ref(snapshot, -1);
return NULL;
}
@@ -268,6 +274,16 @@ struct ast_bridge_snapshot *ast_bridge_snapshot_create(struct ast_bridge *bridge
snapshot->capabilities = bridge->technology->capabilities;
snapshot->num_channels = bridge->num_channels;
snapshot->num_active = bridge->num_active;
+ snapshot->video_mode = bridge->softmix.video_mode.mode;
+ if (snapshot->video_mode == AST_BRIDGE_VIDEO_MODE_SINGLE_SRC
+ && bridge->softmix.video_mode.mode_data.single_src_data.chan_vsrc) {
+ ast_string_field_set(snapshot, video_source_id,
+ ast_channel_uniqueid(bridge->softmix.video_mode.mode_data.single_src_data.chan_vsrc));
+ } else if (snapshot->video_mode == AST_BRIDGE_VIDEO_MODE_TALKER_SRC
+ && bridge->softmix.video_mode.mode_data.talker_src_data.chan_vsrc) {
+ ast_string_field_set(snapshot, video_source_id,
+ ast_channel_uniqueid(bridge->softmix.video_mode.mode_data.talker_src_data.chan_vsrc));
+ }
ao2_ref(snapshot, +1);
return snapshot;
@@ -590,18 +606,25 @@ struct ast_json *ast_bridge_snapshot_to_json(
return NULL;
}
- json_bridge = ast_json_pack("{s: s, s: s, s: s, s: s, s: s, s: s, s: o}",
+ json_bridge = ast_json_pack("{s: s, s: s, s: s, s: s, s: s, s: s, s: o, s: s}",
"id", snapshot->uniqueid,
"technology", snapshot->technology,
"bridge_type", capability2str(snapshot->capabilities),
"bridge_class", snapshot->subclass,
"creator", snapshot->creator,
"name", snapshot->name,
- "channels", json_channels);
+ "channels", json_channels,
+ "video_mode", ast_bridge_video_mode_to_string(snapshot->video_mode));
if (!json_bridge) {
return NULL;
}
+ if (snapshot->video_mode != AST_BRIDGE_VIDEO_MODE_NONE
+ && !ast_strlen_zero(snapshot->video_source_id)) {
+ ast_json_object_set(json_bridge, "video_source_id",
+ ast_json_string_create(snapshot->video_source_id));
+ }
+
return ast_json_ref(json_bridge);
}
diff --git a/res/ari/ari_model_validators.c b/res/ari/ari_model_validators.c
index 9fd844c4d..ea5a88599 100644
--- a/res/ari/ari_model_validators.c
+++ b/res/ari/ari_model_validators.c
@@ -1367,6 +1367,24 @@ int ast_ari_validate_bridge(struct ast_json *json)
res = 0;
}
} else
+ if (strcmp("video_mode", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ prop_is_valid = ast_ari_validate_string(
+ ast_json_object_iter_value(iter));
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI Bridge field video_mode failed validation\n");
+ res = 0;
+ }
+ } else
+ if (strcmp("video_source_id", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ prop_is_valid = ast_ari_validate_string(
+ ast_json_object_iter_value(iter));
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI Bridge field video_source_id failed validation\n");
+ res = 0;
+ }
+ } else
{
ast_log(LOG_ERROR,
"ARI Bridge has undocumented field %s\n",
@@ -2722,6 +2740,103 @@ ari_validator ast_ari_validate_bridge_merged_fn(void)
return ast_ari_validate_bridge_merged;
}
+int ast_ari_validate_bridge_video_source_changed(struct ast_json *json)
+{
+ int res = 1;
+ struct ast_json_iter *iter;
+ int has_type = 0;
+ int has_application = 0;
+ int has_bridge = 0;
+
+ for (iter = ast_json_object_iter(json); iter; iter = ast_json_object_iter_next(json, iter)) {
+ if (strcmp("asterisk_id", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ prop_is_valid = ast_ari_validate_string(
+ ast_json_object_iter_value(iter));
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI BridgeVideoSourceChanged field asterisk_id failed validation\n");
+ res = 0;
+ }
+ } else
+ if (strcmp("type", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ has_type = 1;
+ prop_is_valid = ast_ari_validate_string(
+ ast_json_object_iter_value(iter));
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI BridgeVideoSourceChanged field type failed validation\n");
+ res = 0;
+ }
+ } else
+ if (strcmp("application", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ has_application = 1;
+ prop_is_valid = ast_ari_validate_string(
+ ast_json_object_iter_value(iter));
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI BridgeVideoSourceChanged field application failed validation\n");
+ res = 0;
+ }
+ } else
+ if (strcmp("timestamp", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ prop_is_valid = ast_ari_validate_date(
+ ast_json_object_iter_value(iter));
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI BridgeVideoSourceChanged field timestamp failed validation\n");
+ res = 0;
+ }
+ } else
+ if (strcmp("bridge", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ has_bridge = 1;
+ prop_is_valid = ast_ari_validate_bridge(
+ ast_json_object_iter_value(iter));
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI BridgeVideoSourceChanged field bridge failed validation\n");
+ res = 0;
+ }
+ } else
+ if (strcmp("old_video_source_id", ast_json_object_iter_key(iter)) == 0) {
+ int prop_is_valid;
+ prop_is_valid = ast_ari_validate_string(
+ ast_json_object_iter_value(iter));
+ if (!prop_is_valid) {
+ ast_log(LOG_ERROR, "ARI BridgeVideoSourceChanged field old_video_source_id failed validation\n");
+ res = 0;
+ }
+ } else
+ {
+ ast_log(LOG_ERROR,
+ "ARI BridgeVideoSourceChanged has undocumented field %s\n",
+ ast_json_object_iter_key(iter));
+ res = 0;
+ }
+ }
+
+ if (!has_type) {
+ ast_log(LOG_ERROR, "ARI BridgeVideoSourceChanged missing required field type\n");
+ res = 0;
+ }
+
+ if (!has_application) {
+ ast_log(LOG_ERROR, "ARI BridgeVideoSourceChanged missing required field application\n");
+ res = 0;
+ }
+
+ if (!has_bridge) {
+ ast_log(LOG_ERROR, "ARI BridgeVideoSourceChanged missing required field bridge\n");
+ res = 0;
+ }
+
+ return res;
+}
+
+ari_validator ast_ari_validate_bridge_video_source_changed_fn(void)
+{
+ return ast_ari_validate_bridge_video_source_changed;
+}
+
int ast_ari_validate_channel_caller_id(struct ast_json *json)
{
int res = 1;
@@ -4928,6 +5043,9 @@ int ast_ari_validate_event(struct ast_json *json)
if (strcmp("BridgeMerged", discriminator) == 0) {
return ast_ari_validate_bridge_merged(json);
} else
+ if (strcmp("BridgeVideoSourceChanged", discriminator) == 0) {
+ return ast_ari_validate_bridge_video_source_changed(json);
+ } else
if (strcmp("ChannelCallerId", discriminator) == 0) {
return ast_ari_validate_channel_caller_id(json);
} else
@@ -5123,6 +5241,9 @@ int ast_ari_validate_message(struct ast_json *json)
if (strcmp("BridgeMerged", discriminator) == 0) {
return ast_ari_validate_bridge_merged(json);
} else
+ if (strcmp("BridgeVideoSourceChanged", discriminator) == 0) {
+ return ast_ari_validate_bridge_video_source_changed(json);
+ } else
if (strcmp("ChannelCallerId", discriminator) == 0) {
return ast_ari_validate_channel_caller_id(json);
} else
diff --git a/res/ari/ari_model_validators.h b/res/ari/ari_model_validators.h
index fcd9fc11c..093e3f4f5 100644
--- a/res/ari/ari_model_validators.h
+++ b/res/ari/ari_model_validators.h
@@ -717,6 +717,24 @@ int ast_ari_validate_bridge_merged(struct ast_json *json);
ari_validator ast_ari_validate_bridge_merged_fn(void);
/*!
+ * \brief Validator for BridgeVideoSourceChanged.
+ *
+ * Notification that the source of video in a bridge has changed.
+ *
+ * \param json JSON object to validate.
+ * \returns True (non-zero) if valid.
+ * \returns False (zero) if invalid.
+ */
+int ast_ari_validate_bridge_video_source_changed(struct ast_json *json);
+
+/*!
+ * \brief Function pointer to ast_ari_validate_bridge_video_source_changed().
+ *
+ * See \ref ast_ari_model_validators.h for more details.
+ */
+ari_validator ast_ari_validate_bridge_video_source_changed_fn(void);
+
+/*!
* \brief Validator for ChannelCallerId.
*
* Channel changed Caller ID.
@@ -1453,6 +1471,8 @@ ari_validator ast_ari_validate_application_fn(void);
* - id: string (required)
* - name: string (required)
* - technology: string (required)
+ * - video_mode: string
+ * - video_source_id: string
* LiveRecording
* - cause: string
* - duration: int
@@ -1544,6 +1564,13 @@ ari_validator ast_ari_validate_application_fn(void);
* - timestamp: Date
* - bridge: Bridge (required)
* - bridge_from: Bridge (required)
+ * BridgeVideoSourceChanged
+ * - asterisk_id: string
+ * - type: string (required)
+ * - application: string (required)
+ * - timestamp: Date
+ * - bridge: Bridge (required)
+ * - old_video_source_id: string
* ChannelCallerId
* - asterisk_id: string
* - type: string (required)
diff --git a/res/ari/resource_bridges.c b/res/ari/resource_bridges.c
index f5ae1c003..f243086c9 100644
--- a/res/ari/resource_bridges.c
+++ b/res/ari/resource_bridges.c
@@ -35,6 +35,7 @@
#include "asterisk/stasis.h"
#include "asterisk/stasis_bridges.h"
#include "asterisk/stasis_app.h"
+#include "asterisk/stasis_app_impl.h"
#include "asterisk/stasis_app_playback.h"
#include "asterisk/stasis_app_recording.h"
#include "asterisk/stasis_channels.h"
@@ -1003,3 +1004,68 @@ void ast_ari_bridges_create_with_id(struct ast_variable *headers,
ast_ari_response_ok(response,
ast_bridge_snapshot_to_json(snapshot, stasis_app_get_sanitizer()));
}
+
+static int bridge_set_video_source_cb(struct stasis_app_control *control,
+ struct ast_channel *chan, void *data)
+{
+ struct ast_bridge *bridge = data;
+
+ ast_bridge_lock(bridge);
+ ast_bridge_set_single_src_video_mode(bridge, chan);
+ ast_bridge_unlock(bridge);
+
+ return 0;
+}
+
+void ast_ari_bridges_set_video_source(struct ast_variable *headers,
+ struct ast_ari_bridges_set_video_source_args *args, struct ast_ari_response *response)
+{
+ struct ast_bridge *bridge;
+ struct stasis_app_control *control;
+
+ bridge = find_bridge(response, args->bridge_id);
+ if (!bridge) {
+ return;
+ }
+
+ control = find_channel_control(response, args->channel_id);
+ if (!control) {
+ ao2_ref(bridge, -1);
+ return;
+ }
+
+ if (stasis_app_get_bridge(control) != bridge) {
+ ast_ari_response_error(response, 422,
+ "Unprocessable Entity",
+ "Channel not in this bridge");
+ ao2_ref(bridge, -1);
+ ao2_ref(control, -1);
+ return;
+ }
+
+ stasis_app_send_command(control, bridge_set_video_source_cb,
+ ao2_bump(bridge), __ao2_cleanup);
+
+ ao2_ref(bridge, -1);
+ ao2_ref(control, -1);
+
+ ast_ari_response_no_content(response);
+}
+
+void ast_ari_bridges_clear_video_source(struct ast_variable *headers,
+ struct ast_ari_bridges_clear_video_source_args *args, struct ast_ari_response *response)
+{
+ struct ast_bridge *bridge;
+
+ bridge = find_bridge(response, args->bridge_id);
+ if (!bridge) {
+ return;
+ }
+
+ ast_bridge_lock(bridge);
+ ast_bridge_set_talker_src_video_mode(bridge);
+ ast_bridge_unlock(bridge);
+
+ ao2_ref(bridge, -1);
+ ast_ari_response_no_content(response);
+}
diff --git a/res/ari/resource_bridges.h b/res/ari/resource_bridges.h
index 17a3b8365..e75d8e028 100644
--- a/res/ari/resource_bridges.h
+++ b/res/ari/resource_bridges.h
@@ -200,6 +200,34 @@ int ast_ari_bridges_remove_channel_parse_body(
* \param[out] response HTTP response
*/
void ast_ari_bridges_remove_channel(struct ast_variable *headers, struct ast_ari_bridges_remove_channel_args *args, struct ast_ari_response *response);
+/*! Argument struct for ast_ari_bridges_set_video_source() */
+struct ast_ari_bridges_set_video_source_args {
+ /*! Bridge's id */
+ const char *bridge_id;
+ /*! Channel's id */
+ const char *channel_id;
+};
+/*!
+ * \brief Set a channel as the video source in a multi-party mixing bridge. This operation has no effect on bridges with two or fewer participants.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void ast_ari_bridges_set_video_source(struct ast_variable *headers, struct ast_ari_bridges_set_video_source_args *args, struct ast_ari_response *response);
+/*! Argument struct for ast_ari_bridges_clear_video_source() */
+struct ast_ari_bridges_clear_video_source_args {
+ /*! Bridge's id */
+ const char *bridge_id;
+};
+/*!
+ * \brief Removes any explicit video source in a multi-party mixing bridge. This operation has no effect on bridges with two or fewer participants. When no explicit video source is set, talk detection will be used to determine the active video stream.
+ *
+ * \param headers HTTP headers
+ * \param args Swagger parameters
+ * \param[out] response HTTP response
+ */
+void ast_ari_bridges_clear_video_source(struct ast_variable *headers, struct ast_ari_bridges_clear_video_source_args *args, struct ast_ari_response *response);
/*! Argument struct for ast_ari_bridges_start_moh() */
struct ast_ari_bridges_start_moh_args {
/*! Bridge's id */
diff --git a/res/res_agi.c b/res/res_agi.c
index 06e8a03e2..5e047d4c3 100644
--- a/res/res_agi.c
+++ b/res/res_agi.c
@@ -4091,23 +4091,6 @@ static enum agi_result agi_handle_command(struct ast_channel *chan, AGI *agi, ch
return AGI_RESULT_SUCCESS;
}
-AST_LIST_HEAD_NOLOCK(deferred_frames, ast_frame);
-
-static void queue_deferred_frames(struct deferred_frames *deferred_frames,
- struct ast_channel *chan)
-{
- struct ast_frame *f;
-
- if (!AST_LIST_EMPTY(deferred_frames)) {
- ast_channel_lock(chan);
- while ((f = AST_LIST_REMOVE_HEAD(deferred_frames, frame_list))) {
- ast_queue_frame_head(chan, f);
- ast_frfree(f);
- }
- ast_channel_unlock(chan);
- }
-}
-
static enum agi_result run_agi(struct ast_channel *chan, char *request, AGI *agi, int pid, int *status, int dead, int argc, char *argv[])
{
struct ast_channel *c;
@@ -4126,9 +4109,6 @@ static enum agi_result run_agi(struct ast_channel *chan, char *request, AGI *agi
const char *sighup_str;
const char *exit_on_hangup_str;
int exit_on_hangup;
- struct deferred_frames deferred_frames;
-
- AST_LIST_HEAD_INIT_NOLOCK(&deferred_frames);
ast_channel_lock(chan);
sighup_str = pbx_builtin_getvar_helper(chan, "AGISIGHUP");
@@ -4190,20 +4170,8 @@ static enum agi_result run_agi(struct ast_channel *chan, char *request, AGI *agi
/* Write, ignoring errors */
if (write(agi->audio, f->data.ptr, f->datalen) < 0) {
}
- ast_frfree(f);
- } else if (ast_is_deferrable_frame(f)) {
- struct ast_frame *dup_f;
-
- if ((dup_f = ast_frisolate(f))) {
- AST_LIST_INSERT_HEAD(&deferred_frames, dup_f, frame_list);
- }
-
- if (dup_f != f) {
- ast_frfree(f);
- }
- } else {
- ast_frfree(f);
}
+ ast_frfree(f);
}
} else if (outfd > -1) {
size_t len = sizeof(buf);
@@ -4251,8 +4219,6 @@ static enum agi_result run_agi(struct ast_channel *chan, char *request, AGI *agi
buf[buflen - 1] = '\0';
}
- queue_deferred_frames(&deferred_frames, chan);
-
if (agidebug)
ast_verbose("<%s>AGI Rx << %s\n", ast_channel_name(chan), buf);
cmd_status = agi_handle_command(chan, agi, buf, dead);
@@ -4275,8 +4241,6 @@ static enum agi_result run_agi(struct ast_channel *chan, char *request, AGI *agi
}
}
- queue_deferred_frames(&deferred_frames, chan);
-
if (agi->speech) {
ast_speech_destroy(agi->speech);
}
diff --git a/res/res_ari_bridges.c b/res/res_ari_bridges.c
index 29fb07582..0b370c299 100644
--- a/res/res_ari_bridges.c
+++ b/res/res_ari_bridges.c
@@ -767,6 +767,129 @@ fin: __attribute__((unused))
ast_free(args.channel);
return;
}
+/*!
+ * \brief Parameter parsing callback for /bridges/{bridgeId}/videoSource/{channelId}.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void ast_ari_bridges_set_video_source_cb(
+ struct ast_tcptls_session_instance *ser,
+ struct ast_variable *get_params, struct ast_variable *path_vars,
+ struct ast_variable *headers, struct ast_ari_response *response)
+{
+ struct ast_ari_bridges_set_video_source_args args = {};
+ struct ast_variable *i;
+ RAII_VAR(struct ast_json *, body, NULL, ast_json_unref);
+#if defined(AST_DEVMODE)
+ int is_valid;
+ int code;
+#endif /* AST_DEVMODE */
+
+ for (i = path_vars; i; i = i->next) {
+ if (strcmp(i->name, "bridgeId") == 0) {
+ args.bridge_id = (i->value);
+ } else
+ if (strcmp(i->name, "channelId") == 0) {
+ args.channel_id = (i->value);
+ } else
+ {}
+ }
+ ast_ari_bridges_set_video_source(headers, &args, response);
+#if defined(AST_DEVMODE)
+ code = response->response_code;
+
+ switch (code) {
+ case 0: /* Implementation is still a stub, or the code wasn't set */
+ is_valid = response->message == NULL;
+ break;
+ case 500: /* Internal Server Error */
+ case 501: /* Not Implemented */
+ case 404: /* Bridge or Channel not found */
+ case 409: /* Channel not in Stasis application */
+ case 422: /* Channel not in this Bridge */
+ is_valid = 1;
+ break;
+ default:
+ if (200 <= code && code <= 299) {
+ is_valid = ast_ari_validate_void(
+ response->message);
+ } else {
+ ast_log(LOG_ERROR, "Invalid error response %d for /bridges/{bridgeId}/videoSource/{channelId}\n", code);
+ is_valid = 0;
+ }
+ }
+
+ if (!is_valid) {
+ ast_log(LOG_ERROR, "Response validation failed for /bridges/{bridgeId}/videoSource/{channelId}\n");
+ ast_ari_response_error(response, 500,
+ "Internal Server Error", "Response validation failed");
+ }
+#endif /* AST_DEVMODE */
+
+fin: __attribute__((unused))
+ return;
+}
+/*!
+ * \brief Parameter parsing callback for /bridges/{bridgeId}/videoSource.
+ * \param get_params GET parameters in the HTTP request.
+ * \param path_vars Path variables extracted from the request.
+ * \param headers HTTP headers.
+ * \param[out] response Response to the HTTP request.
+ */
+static void ast_ari_bridges_clear_video_source_cb(
+ struct ast_tcptls_session_instance *ser,
+ struct ast_variable *get_params, struct ast_variable *path_vars,
+ struct ast_variable *headers, struct ast_ari_response *response)
+{
+ struct ast_ari_bridges_clear_video_source_args args = {};
+ struct ast_variable *i;
+ RAII_VAR(struct ast_json *, body, NULL, ast_json_unref);
+#if defined(AST_DEVMODE)
+ int is_valid;
+ int code;
+#endif /* AST_DEVMODE */
+
+ for (i = path_vars; i; i = i->next) {
+ if (strcmp(i->name, "bridgeId") == 0) {
+ args.bridge_id = (i->value);
+ } else
+ {}
+ }
+ ast_ari_bridges_clear_video_source(headers, &args, response);
+#if defined(AST_DEVMODE)
+ code = response->response_code;
+
+ switch (code) {
+ case 0: /* Implementation is still a stub, or the code wasn't set */
+ is_valid = response->message == NULL;
+ break;
+ case 500: /* Internal Server Error */
+ case 501: /* Not Implemented */
+ case 404: /* Bridge not found */
+ is_valid = 1;
+ break;
+ default:
+ if (200 <= code && code <= 299) {
+ is_valid = ast_ari_validate_void(
+ response->message);
+ } else {
+ ast_log(LOG_ERROR, "Invalid error response %d for /bridges/{bridgeId}/videoSource\n", code);
+ is_valid = 0;
+ }
+ }
+
+ if (!is_valid) {
+ ast_log(LOG_ERROR, "Response validation failed for /bridges/{bridgeId}/videoSource\n");
+ ast_ari_response_error(response, 500,
+ "Internal Server Error", "Response validation failed");
+ }
+#endif /* AST_DEVMODE */
+
+fin: __attribute__((unused))
+ return;
+}
int ast_ari_bridges_start_moh_parse_body(
struct ast_json *body,
struct ast_ari_bridges_start_moh_args *args)
@@ -1469,6 +1592,25 @@ static struct stasis_rest_handlers bridges_bridgeId_removeChannel = {
.children = { }
};
/*! \brief REST handler for /api-docs/bridges.json */
+static struct stasis_rest_handlers bridges_bridgeId_videoSource_channelId = {
+ .path_segment = "channelId",
+ .is_wildcard = 1,
+ .callbacks = {
+ [AST_HTTP_POST] = ast_ari_bridges_set_video_source_cb,
+ },
+ .num_children = 0,
+ .children = { }
+};
+/*! \brief REST handler for /api-docs/bridges.json */
+static struct stasis_rest_handlers bridges_bridgeId_videoSource = {
+ .path_segment = "videoSource",
+ .callbacks = {
+ [AST_HTTP_DELETE] = ast_ari_bridges_clear_video_source_cb,
+ },
+ .num_children = 1,
+ .children = { &bridges_bridgeId_videoSource_channelId, }
+};
+/*! \brief REST handler for /api-docs/bridges.json */
static struct stasis_rest_handlers bridges_bridgeId_moh = {
.path_segment = "moh",
.callbacks = {
@@ -1515,8 +1657,8 @@ static struct stasis_rest_handlers bridges_bridgeId = {
[AST_HTTP_GET] = ast_ari_bridges_get_cb,
[AST_HTTP_DELETE] = ast_ari_bridges_destroy_cb,
},
- .num_children = 5,
- .children = { &bridges_bridgeId_addChannel,&bridges_bridgeId_removeChannel,&bridges_bridgeId_moh,&bridges_bridgeId_play,&bridges_bridgeId_record, }
+ .num_children = 6,
+ .children = { &bridges_bridgeId_addChannel,&bridges_bridgeId_removeChannel,&bridges_bridgeId_videoSource,&bridges_bridgeId_moh,&bridges_bridgeId_play,&bridges_bridgeId_record, }
};
/*! \brief REST handler for /api-docs/bridges.json */
static struct stasis_rest_handlers bridges = {
diff --git a/res/res_format_attr_opus.c b/res/res_format_attr_opus.c
index a3e48e3de..81a07a1f6 100644
--- a/res/res_format_attr_opus.c
+++ b/res/res_format_attr_opus.c
@@ -156,7 +156,8 @@ static struct ast_format *opus_parse_sdp_fmtp(const struct ast_format *format, c
static void opus_generate_sdp_fmtp(const struct ast_format *format, unsigned int payload, struct ast_str **str)
{
struct opus_attr *attr = ast_format_get_attribute_data(format);
- int size;
+ int base_fmtp_size;
+ int original_size;
if (!attr) {
/*
@@ -167,7 +168,8 @@ static void opus_generate_sdp_fmtp(const struct ast_format *format, unsigned int
attr = &default_opus_attr;
}
- size = ast_str_append(str, 0, "a=fmtp:%u ", payload);
+ original_size = ast_str_strlen(*str);
+ base_fmtp_size = ast_str_append(str, 0, "a=fmtp:%u ", payload);
if (CODEC_OPUS_DEFAULT_SAMPLE_RATE != attr->maxplayrate) {
ast_str_append(str, 0, "%s=%d;",
@@ -209,8 +211,8 @@ static void opus_generate_sdp_fmtp(const struct ast_format *format, unsigned int
CODEC_OPUS_ATTR_DTX, attr->dtx);
}
- if (size == ast_str_strlen(*str)) {
- ast_str_reset(*str);
+ if (base_fmtp_size == ast_str_strlen(*str) - original_size) {
+ ast_str_truncate(*str, original_size);
} else {
ast_str_truncate(*str, -1);
ast_str_append(str, 0, "\r\n");
diff --git a/res/stasis/app.c b/res/stasis/app.c
index ac316fac0..0b75ed5d7 100644
--- a/res/stasis/app.c
+++ b/res/stasis/app.c
@@ -698,6 +698,13 @@ static void sub_bridge_update_handler(void *data,
json = simple_bridge_event("BridgeDestroyed", old_snapshot, tv);
} else if (!old_snapshot) {
json = simple_bridge_event("BridgeCreated", new_snapshot, tv);
+ } else if (new_snapshot && old_snapshot
+ && strcmp(new_snapshot->video_source_id, old_snapshot->video_source_id)) {
+ json = simple_bridge_event("BridgeVideoSourceChanged", new_snapshot, tv);
+ if (json && !ast_strlen_zero(old_snapshot->video_source_id)) {
+ ast_json_object_set(json, "old_video_source_id",
+ ast_json_string_create(old_snapshot->video_source_id));
+ }
}
if (json) {
diff --git a/rest-api/api-docs/bridges.json b/rest-api/api-docs/bridges.json
index ab2c6c2d5..8289b43e1 100644
--- a/rest-api/api-docs/bridges.json
+++ b/rest-api/api-docs/bridges.json
@@ -241,6 +241,78 @@
]
},
{
+ "path": "/bridges/{bridgeId}/videoSource/{channelId}",
+ "description": "Set a channel as the video source in a multi-party bridge",
+ "operations": [
+ {
+ "httpMethod": "POST",
+ "summary": "Set a channel as the video source in a multi-party mixing bridge. This operation has no effect on bridges with two or fewer participants.",
+ "nickname": "setVideoSource",
+ "responseClass": "void",
+ "parameters": [
+ {
+ "name": "bridgeId",
+ "description": "Bridge's id",
+ "paramType": "path",
+ "required": true,
+ "allowMultiple": false,
+ "dataType": "string"
+ },
+ {
+ "name": "channelId",
+ "description": "Channel's id",
+ "paramType": "path",
+ "required": true,
+ "allowMultiple": false,
+ "dataType": "string"
+ }
+ ],
+ "errorResponses": [
+ {
+ "code": 404,
+ "reason": "Bridge or Channel not found"
+ },
+ {
+ "code": 409,
+ "reason": "Channel not in Stasis application"
+ },
+ {
+ "code": 422,
+ "reason": "Channel not in this Bridge"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "path": "/bridges/{bridgeId}/videoSource",
+ "description": "Removes any explicit video source",
+ "operations": [
+ {
+ "httpMethod": "DELETE",
+ "summary": "Removes any explicit video source in a multi-party mixing bridge. This operation has no effect on bridges with two or fewer participants. When no explicit video source is set, talk detection will be used to determine the active video stream.",
+ "nickname": "clearVideoSource",
+ "responseClass": "void",
+ "parameters": [
+ {
+ "name": "bridgeId",
+ "description": "Bridge's id",
+ "paramType": "path",
+ "required": true,
+ "allowMultiple": false,
+ "dataType": "string"
+ }
+ ],
+ "errorResponses": [
+ {
+ "code": 404,
+ "reason": "Bridge not found"
+ }
+ ]
+ }
+ ]
+ },
+ {
"path": "/bridges/{bridgeId}/moh",
"description": "Play music on hold to a bridge",
"operations": [
@@ -649,6 +721,16 @@
"type": "List[string]",
"description": "Ids of channels participating in this bridge",
"required": true
+ },
+ "video_mode": {
+ "type": "string",
+ "description": "The video mode the bridge is using. One of 'none', 'talker', or 'single'.",
+ "required": false
+ },
+ "video_source_id": {
+ "type": "string",
+ "description": "The ID of the channel that is the source of video in this bridge, if one exists.",
+ "required": false
}
}
}
diff --git a/rest-api/api-docs/events.json b/rest-api/api-docs/events.json
index 4ef1d21a4..f99f52e67 100644
--- a/rest-api/api-docs/events.json
+++ b/rest-api/api-docs/events.json
@@ -162,6 +162,7 @@
"BridgeMerged",
"BridgeBlindTransfer",
"BridgeAttendedTransfer",
+ "BridgeVideoSourceChanged",
"ChannelCreated",
"ChannelDestroyed",
"ChannelEnteredBridge",
@@ -370,6 +371,20 @@
}
}
},
+ "BridgeVideoSourceChanged": {
+ "id": "BridgeVideoSourceChanged",
+ "description": "Notification that the source of video in a bridge has changed.",
+ "properties": {
+ "bridge": {
+ "required": true,
+ "type": "Bridge"
+ },
+ "old_video_source_id": {
+ "required": false,
+ "type": "string"
+ }
+ }
+ },
"BridgeBlindTransfer": {
"id": "BridgeBlindTransfer",
"description": "Notification that a blind transfer has occurred.",
diff --git a/tests/test_file.c b/tests/test_file.c
index 64bad9218..bb8a99584 100644
--- a/tests/test_file.c
+++ b/tests/test_file.c
@@ -21,7 +21,10 @@
<support_level>core</support_level>
***/
+
#include "asterisk.h"
+#include <sys/stat.h>
+#include <stdio.h>
#include "asterisk/file.h"
#include "asterisk/paths.h"
@@ -115,6 +118,17 @@ static char *test_files_get_one(struct _filenames *filenames, int num)
static int handle_find_file(const char *dir_name, const char *filename, void *obj)
{
+ struct stat statbuf;
+ char *full_path = ast_alloca(strlen(dir_name) + strlen(filename) + 2);
+
+ sprintf(full_path, "%s/%s", dir_name, filename);
+
+ errno = 0;
+ if (stat(full_path, &statbuf)) {
+ ast_log(LOG_ERROR, "Error reading path stats - %s: %s\n",
+ full_path, strerror(errno));
+ return 0;
+ }
/* obj contains the name of the file we are looking for */
return strcmp(obj, filename) ? 0 : FOUND;
}