framehooks: Add callback for determining if a hook is consuming frames of a specific type.

In the past framehooks have had no capability to determine what frame types a hook
is actually interested in consuming. This has meant that code has had to assume they
want all frames, thus preventing native bridging.

This change adds a callback which allows a framehook to be queried for whether it
is consuming a frame of a specific type. The native RTP bridging module has also
been updated to take advantange of this, allowing native bridging to occur when
previously it would not.

ASTERISK-23497 #comment Reported by: Etienne Lessard
ASTERISK-23497 #close

Review: https://reviewboard.asterisk.org/r/3522/
........

Merged revisions 413681 from http://svn.asterisk.org/svn/asterisk/branches/12


git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@413682 65c4cc65-6c06-0410-ace0-fbb531ad65f3
changes/97/197/1
Joshua Colp 11 years ago
parent e2ed86e4ca
commit d134150be2

@ -288,10 +288,16 @@ static struct ast_frame *native_rtp_framehook(struct ast_channel *chan, struct a
return f;
}
/*! \brief Callback function which informs upstream if we are consuming a frame of a specific type */
static int native_rtp_framehook_consume(void *data, enum ast_frame_type type)
{
return (type == AST_FRAME_CONTROL ? 1 : 0);
}
/*! \brief Internal helper function which checks whether the channels are compatible with our native bridging */
static int native_rtp_bridge_capable(struct ast_channel *chan)
{
return !ast_channel_has_audio_frame_or_monitor(chan);
return !ast_channel_has_hook_requiring_audio(chan);
}
static int native_rtp_bridge_compatible(struct ast_bridge *bridge)
@ -392,6 +398,7 @@ static int native_rtp_bridge_framehook_attach(struct ast_bridge_channel *bridge_
static struct ast_framehook_interface hook = {
.version = AST_FRAMEHOOK_INTERFACE_VERSION,
.event_cb = native_rtp_framehook,
.consume_cb = native_rtp_framehook_consume,
};
if (!data) {

@ -1049,8 +1049,8 @@ enum {
*/
AST_SOFTHANGUP_EXPLICIT = (1 << 5),
/*!
* Used to break a bridge so the channel can be spied upon
* instead of actually hanging up.
* Used to request that the bridge core re-evaluate the current
* bridging technology in use by the bridge this channel is in.
*/
AST_SOFTHANGUP_UNBRIDGE = (1 << 6),
/*!
@ -4401,6 +4401,16 @@ const char *ast_channel_oldest_linkedid(const char *a, const char *b);
*/
int ast_channel_has_audio_frame_or_monitor(struct ast_channel *chan);
/*!
* \brief Check if the channel has any active hooks that require audio.
* \since 12.3.0
*
* \param chan The channel to check.
*
* \retval non-zero if channel has active audiohooks, audio framehooks, or monitor.
*/
int ast_channel_has_hook_requiring_audio(struct ast_channel *chan);
/*!
* \brief Removes the trailing identifiers from a channel name string
* \since 12.0.0

@ -25,114 +25,114 @@
\page AstFrameHookAPI Asterisk FrameHook API
\section FrameHookFunctionality How FrameHooks Work
FrameHooks work by intercepting all frames being written and read off
a channel and allowing those frames to be viewed and manipulated within a
call back function. Frame interception occurs before any processing is
done on the frame, which means this hook can be used to transparently
manipulate a frame before it is read from the channel or written
to the tech_pvt. This API can be thought of as a layer between the
channel API and the Asterisk core when going in the READ direction, and
as a layer between the Channel API and the tech_pvt when going in the
WRITE direction.
FrameHooks work by intercepting all frames being written and read off
a channel and allowing those frames to be viewed and manipulated within a
call back function. Frame interception occurs before any processing is
done on the frame, which means this hook can be used to transparently
manipulate a frame before it is read from the channel or written
to the tech_pvt. This API can be thought of as a layer between the
channel API and the Asterisk core when going in the READ direction, and
as a layer between the Channel API and the tech_pvt when going in the
WRITE direction.
\section FrameHookAPIUsage How to Use an FrameHook
Attaching and detaching an FrameHook to a channel is very simple. There are only
two functions involved, ast_framehook_attach() which will return an id representing
the new FrameHook on the channel, and ast_framehook_detach() which signals the
FrameHook for detachment and destruction. Below is detailed information each of these
functions and their usage.
Attaching and detaching an FrameHook to a channel is very simple. There are only
two functions involved, ast_framehook_attach() which will return an id representing
the new FrameHook on the channel, and ast_framehook_detach() which signals the
FrameHook for detachment and destruction. Below is detailed information each of these
functions and their usage.
\code
struct ast_framehook_interface interface = {
.version = AST_FRAMEHOOK_INTERFACE_VERSION,
.event_cb = hook_event_cb,
.destroy_cb = hook_destroy_cb,
.data = data, // where the data ptr points to any custom data used later by the hook cb.
};
int id = ast_framehook_attach(channel, &interface);
struct ast_framehook_interface interface = {
.version = AST_FRAMEHOOK_INTERFACE_VERSION,
.event_cb = hook_event_cb,
.destroy_cb = hook_destroy_cb,
.data = data, // where the data ptr points to any custom data used later by the hook cb.
};
int id = ast_framehook_attach(channel, &interface);
\endcode
The ast_framehook_attach() function creates and attaches a new FrameHook onto
a channel. Once attached to the channel, the FrameHook will call the event_callback
function each time a frame is written or read on the channel. A custom data
pointer can be provided to this function to store on the FrameHook as well. This
pointer can be used to keep up with any statefull information associated with the FrameHook
and is provided during the event_callback function. The destroy_callback function is optional.
This function exists so any custom data stored on the FrameHook can be destroyed before
the Framehook if destroyed.
The ast_framehook_attach() function creates and attaches a new FrameHook onto
a channel. Once attached to the channel, the FrameHook will call the event_callback
function each time a frame is written or read on the channel. A custom data
pointer can be provided to this function to store on the FrameHook as well. This
pointer can be used to keep up with any statefull information associated with the FrameHook
and is provided during the event_callback function. The destroy_callback function is optional.
This function exists so any custom data stored on the FrameHook can be destroyed before
the Framehook if destroyed.
\code
ast_framehook_detach(channel, id);
ast_framehook_detach(channel, id);
\endcode
The ast_framehook_detach() function signals the FrameHook represented by an id to
be detached and destroyed on a channel. Since it is possible this function may be called
during the FrameHook's event callback, it is impossible to synchronously detach the
FrameHook from the channel during this function call. It is guaranteed that the next
event proceeding the ast_framehook_detach() will be of type AST_FRAMEHOOK_EVENT_DETACH,
and that after that event occurs no other event will ever be issued for that FrameHook.
Once the FrameHook is destroyed, the destroy callback function will be called if it was
provided. Note that if this function is never called, the FrameHook will be detached
on channel destruction.
The ast_framehook_detach() function signals the FrameHook represented by an id to
be detached and destroyed on a channel. Since it is possible this function may be called
during the FrameHook's event callback, it is impossible to synchronously detach the
FrameHook from the channel during this function call. It is guaranteed that the next
event proceeding the ast_framehook_detach() will be of type AST_FRAMEHOOK_EVENT_DETACH,
and that after that event occurs no other event will ever be issued for that FrameHook.
Once the FrameHook is destroyed, the destroy callback function will be called if it was
provided. Note that if this function is never called, the FrameHook will be detached
on channel destruction.
\section FrameHookAPICodeExample FrameHook Example Code
The example code below attaches an FrameHook on a channel, and then detachs it when
the first ast_frame is read or written to the event callback function. The Framehook's id
is stored on the FrameHook's data pointer so it can be detached within the callback.
The example code below attaches an FrameHook on a channel, and then detachs it when
the first ast_frame is read or written to the event callback function. The Framehook's id
is stored on the FrameHook's data pointer so it can be detached within the callback.
\code
static void destroy_cb(void *data) {
ast_free(data);
}
static struct ast_frame *event_cb(struct ast_channel *chan,
struct ast_frame *frame,
enum ast_framehook_event event,
void *data) {
int *id = data;
if (!frame) {
return frame;
}
if (event == AST_FRAMEHOOK_EVENT_WRITE) {
ast_log(LOG_NOTICE, "YAY we received a frame in the write direction, Type: %d\n", frame->frametype)
ast_framehook_detach(chan, id); // the channel is guaranteed to be locked during this function call.
} else if (event == AST_FRAMEHOOK_EVENT_READ) {
ast_log(LOG_NOTICE, "YAY we received a frame in the read direction: Type: %d\n", frame->frametype);
ast_framehook_detach(chan, id); // the channel is guaranteed to be locked during this function call.
}
return frame;
{
int some_function()
{
struct ast_framehook_interface interface = {
.version = AST_FRAMEHOOK_INTERFACE_VERSION,
.event_cb = hook_event_cb,
.destroy_cb = hook_destroy_cb,
};
int *id = ast_calloc(1, sizeof(int));
if (!id) {
return -1;
}
interface.data = id; // This data will be returned to us in the callbacks.
ast_channel_lock(chan);
*id = ast_framehook_attach(chan, &interface);
ast_channel_unlock(chan);
if (*id < 0) {
// framehook attach failed, free data
ast_free(id);
return -1;
}
return 0;
}
static void destroy_cb(void *data) {
ast_free(data);
}
static struct ast_frame *event_cb(struct ast_channel *chan,
struct ast_frame *frame,
enum ast_framehook_event event,
void *data) {
int *id = data;
if (!frame) {
return frame;
}
if (event == AST_FRAMEHOOK_EVENT_WRITE) {
ast_log(LOG_NOTICE, "YAY we received a frame in the write direction, Type: %d\n", frame->frametype)
ast_framehook_detach(chan, id); // the channel is guaranteed to be locked during this function call.
} else if (event == AST_FRAMEHOOK_EVENT_READ) {
ast_log(LOG_NOTICE, "YAY we received a frame in the read direction: Type: %d\n", frame->frametype);
ast_framehook_detach(chan, id); // the channel is guaranteed to be locked during this function call.
}
return frame;
{
int some_function()
{
struct ast_framehook_interface interface = {
.version = AST_FRAMEHOOK_INTERFACE_VERSION,
.event_cb = hook_event_cb,
.destroy_cb = hook_destroy_cb,
};
int *id = ast_calloc(1, sizeof(int));
if (!id) {
return -1;
}
interface.data = id; // This data will be returned to us in the callbacks.
ast_channel_lock(chan);
*id = ast_framehook_attach(chan, &interface);
ast_channel_unlock(chan);
if (*id < 0) {
// framehook attach failed, free data
ast_free(id);
return -1;
}
return 0;
}
\endcode
*/
@ -199,7 +199,20 @@ typedef struct ast_frame *(*ast_framehook_event_callback)(
*/
typedef void (*ast_framehook_destroy_callback)(void *data);
#define AST_FRAMEHOOK_INTERFACE_VERSION 1
/*!
* \brief This callback is called to determine if the framehook is currently consuming
* frames of a given type
* \since 12
*
* \param data, The data pointer provided at framehook initilization.
* \param type, The type of frame.
*
* \return 0 if frame type is being ignored
* \return 1 if frame type is not being ignored
*/
typedef int (*ast_framehook_consume_callback)(void *data, enum ast_frame_type type);
#define AST_FRAMEHOOK_INTERFACE_VERSION 2
/*! This interface is required for attaching a framehook to a channel. */
struct ast_framehook_interface {
/*! framehook interface version number */
@ -209,6 +222,10 @@ struct ast_framehook_interface {
/*! destroy_cb is optional. This function is called immediately before the framehook
* is destroyed to allow for stored_data cleanup. */
ast_framehook_destroy_callback destroy_cb;
/*! consume_cb is optional. This function is called to query whether the framehook is consuming
* frames of a specific type at this time. If this callback is not implemented it is assumed that the
* framehook will consume frames of all types. */
ast_framehook_consume_callback consume_cb;
/*! This pointer can represent any custom data to be stored on the !framehook. This
* data pointer will be provided during each event callback which allows the framehook
* to store any stateful data associated with the application using the hook. */
@ -323,4 +340,19 @@ int ast_framehook_list_is_empty(struct ast_framehook_list *framehooks);
*/
int ast_framehook_list_contains_no_active(struct ast_framehook_list *framehooks);
/*!
* \brief Determine if a framehook list is free of active framehooks consuming a specific type of frame
* \since 12.3.0
* \pre The channel must be locked during this function call.
*
* \param framehooks the framehook list
* \retval 0, not empty
* \retval 1, is empty (aside from dying framehooks)
*
* \note This function is very similar to ast_framehook_list_is_empty, but it checks individual
* framehooks to see if they have been marked for destruction and doesn't count them if they are.
*/
int ast_framehook_list_contains_no_active_of_type(struct ast_framehook_list *framehooks,
enum ast_frame_type type);
#endif /* _AST_FRAMEHOOK_H */

@ -2614,6 +2614,12 @@ static struct ast_frame *transfer_target_framehook_cb(struct ast_channel *chan,
return frame;
}
/*! \brief Callback function which informs upstream if we are consuming a frame of a specific type */
static int transfer_target_framehook_consume(void *data, enum ast_frame_type type)
{
return (type == AST_FRAME_CONTROL ? 1 : 0);
}
static void transfer_target_framehook_destroy_cb(void *data)
{
struct attended_transfer_properties *props = data;
@ -2847,6 +2853,7 @@ static int attach_framehook(struct attended_transfer_properties *props, struct a
.version = AST_FRAMEHOOK_INTERFACE_VERSION,
.event_cb = transfer_target_framehook_cb,
.destroy_cb = transfer_target_framehook_destroy_cb,
.consume_cb = transfer_target_framehook_consume,
};
ao2_ref(props, +1);

@ -2668,6 +2668,13 @@ int ast_channel_has_audio_frame_or_monitor(struct ast_channel *chan)
|| !ast_framehook_list_contains_no_active(ast_channel_framehooks(chan));
}
int ast_channel_has_hook_requiring_audio(struct ast_channel *chan)
{
return ast_channel_monitor(chan)
|| !ast_audiohook_write_list_empty(ast_channel_audiohooks(chan))
|| !ast_framehook_list_contains_no_active_of_type(ast_channel_framehooks(chan), AST_FRAME_VOICE);
}
static void destroy_hooks(struct ast_channel *chan)
{
if (ast_channel_audiohooks(chan)) {

@ -160,6 +160,10 @@ int ast_framehook_attach(struct ast_channel *chan, struct ast_framehook_interfac
ast_frfree(frame);
}
if (ast_channel_is_bridged(chan)) {
ast_softhangup_nolock(chan, AST_SOFTHANGUP_UNBRIDGE);
}
return framehook->id;
}
@ -214,6 +218,12 @@ int ast_framehook_list_is_empty(struct ast_framehook_list *framehooks)
}
int ast_framehook_list_contains_no_active(struct ast_framehook_list *framehooks)
{
return ast_framehook_list_contains_no_active_of_type(framehooks, 0);
}
int ast_framehook_list_contains_no_active_of_type(struct ast_framehook_list *framehooks,
enum ast_frame_type type)
{
struct ast_framehook *cur;
@ -229,6 +239,9 @@ int ast_framehook_list_contains_no_active(struct ast_framehook_list *framehooks)
if (cur->detach_and_destroy_me) {
continue;
}
if (type && cur->i.consume_cb && !cur->i.consume_cb(cur->i.data, type)) {
continue;
}
return 0;
}

Loading…
Cancel
Save