dect
/
asterisk
Archived
13
0
Fork 0

New HD ConfBridge conferencing application.

Includes a new highly optimized and customizable
ConfBridge application capable of mixing audio at
sample rates ranging from 8khz-192khz.

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



git-svn-id: http://svn.digium.com/svn/asterisk/trunk@314598 f38db490-d61c-443f-a65b-d21fe96a405b
This commit is contained in:
dvossel 2011-04-21 18:11:40 +00:00
parent c064284e6c
commit c7b7b920af
18 changed files with 4890 additions and 635 deletions

View File

@ -58,6 +58,13 @@ CODECS
* Ability to define custom SILK formats in codecs.conf.
* Addition of speex32 audio format with translation.
ConfBridge
--------------------------
* New highly optimized and customizable ConfBridge application capable of
mixing audio at sample rates ranging from 8khz-96khz.
* CONFBRIDGE dialplan function capable of creating dynamic ConfBridge user
and bridge profiles on a channel.
Dialplan Variables
------------------
* Added ASTETCDIR, ASTMODDIR, ASTVARLIBDIR, ASTDBDIR, ASTKEYDIR, ASTDATADIR,

View File

@ -21,6 +21,10 @@
From 1.8 to 1.10:
ConfBridge
- ConfBridge's dialplan arguments have changed and are not
backwards compatible.
HTTP:
- A bindaddr must be specified in order for the HTTP server
to run. Previous versions would default to 0.0.0.0 if no

View File

@ -27,6 +27,12 @@ all: _all
include $(ASTTOPDIR)/Makefile.moddir_rules
clean::
rm -f confbridge/*.o confbridge/*.i
$(if $(filter app_confbridge,$(EMBEDDED_MODS)),modules.link,app_confbridge.so): $(subst .c,.o,$(wildcard confbridge/*.c))
$(subst .c,.o,$(wildcard confbridge/*.c)): _ASTCFLAGS+=$(call MOD_ASTCFLAGS,app_confbridge)
ifneq ($(findstring $(OSARCH), mingw32 cygwin ),)
LIBS+= -lres_features.so -lres_ael_share.so -lres_monitor.so -lres_speech.so
LIBS+= -lres_smdi.so

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,320 @@
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2011, Digium, Inc.
*
* David Vossel <dvossel@digium.com>
* Joshua Colp <jcolp@digium.com>
*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree.
*/
#ifndef _CONFBRIDGE_H
#define _CONFBRIDGE_H
#include "asterisk.h"
#include "asterisk/app.h"
#include "asterisk/logger.h"
#include "asterisk/linkedlists.h"
#include "asterisk/channel.h"
#include "asterisk/bridging.h"
#include "asterisk/bridging_features.h"
/* Maximum length of a conference bridge name */
#define MAX_CONF_NAME 32
/* Maximum length of a conference pin */
#define MAX_PIN 80
#define DEFAULT_USER_PROFILE "default_user"
#define DEFAULT_BRIDGE_PROFILE "default_bridge"
#define DEFAULT_TALKING_THRESHOLD 160
#define DEFAULT_SILENCE_THRESHOLD 2500
enum user_profile_flags {
USER_OPT_ADMIN = (1 << 0), /*!< Set if the caller is an administrator */
USER_OPT_NOONLYPERSON = (1 << 1), /*!< Set if the "you are currently the only person in this conference" sound file should not be played */
USER_OPT_MARKEDUSER = (1 << 2), /*!< Set if the caller is a marked user */
USER_OPT_STARTMUTED = (1 << 3), /*!< Set if the caller should be initially set muted */
USER_OPT_MUSICONHOLD = (1 << 4), /*!< Set if music on hold should be played if nobody else is in the conference bridge */
USER_OPT_QUIET = (1 << 5), /*!< Set if no audio prompts should be played */
USER_OPT_ANNOUNCEUSERCOUNT = (1 << 6), /*!< Set if the number of users should be announced to the caller */
USER_OPT_WAITMARKED = (1 << 7), /*!< Set if the user must wait for a marked user before starting */
USER_OPT_ENDMARKED = (1 << 8), /*!< Set if the user should be kicked after the last Marked user exits */
USER_OPT_DENOISE = (1 << 9), /*!< Sets if denoise filter should be used on audio before mixing. */
USER_OPT_ANNOUNCE_JOIN_LEAVE = (1 << 10), /*!< Sets if the user's name should be recorded and announced on join and leave. */
USER_OPT_TALKER_DETECT = (1 << 11), /*!< Sets if start and stop talking events should generated for this user over AMI. */
USER_OPT_DROP_SILENCE = (1 << 12), /*!< Sets if silence should be dropped from the mix or not. */
USER_OPT_DTMF_PASS = (1 << 13), /*!< Sets if dtmf should be passed into the conference or not */
USER_OPT_ANNOUNCEUSERCOUNTALL = (1 << 14), /*!< Sets if the number of users should be announced to everyone. */
USER_OPT_JITTERBUFFER = (1 << 15), /*!< Places a jitterbuffer on the user. */
};
enum bridge_profile_flags {
BRIDGE_OPT_RECORD_CONFERENCE = (1 << 0), /*!< Set if the conference should be recorded */
};
enum conf_menu_action_id {
MENU_ACTION_TOGGLE_MUTE = 1,
MENU_ACTION_PLAYBACK,
MENU_ACTION_PLAYBACK_AND_CONTINUE,
MENU_ACTION_INCREASE_LISTENING,
MENU_ACTION_DECREASE_LISTENING,
MENU_ACTION_RESET_LISTENING,
MENU_ACTION_RESET_TALKING,
MENU_ACTION_INCREASE_TALKING,
MENU_ACTION_DECREASE_TALKING,
MENU_ACTION_DIALPLAN_EXEC,
MENU_ACTION_ADMIN_TOGGLE_LOCK,
MENU_ACTION_ADMIN_KICK_LAST,
MENU_ACTION_LEAVE,
MENU_ACTION_NOOP,
};
/*! The conference menu action contains both
* the action id that represents the action that
* must take place, along with any data associated
* with that action. */
struct conf_menu_action {
enum conf_menu_action_id id;
union {
char playback_file[PATH_MAX];
struct {
char context[AST_MAX_CONTEXT];
char exten[AST_MAX_EXTENSION];
int priority;
} dialplan_args;
} data;
AST_LIST_ENTRY(conf_menu_action) action;
};
/*! Conference menu entries contain the DTMF sequence
* and the list of actions that are associated with that
* sequence. */
struct conf_menu_entry {
/*! the DTMF sequence that triggers the actions */
char dtmf[MAXIMUM_DTMF_FEATURE_STRING];
/*! The actions associated with this menu entry. */
AST_LIST_HEAD_NOLOCK(, conf_menu_action) actions;
AST_LIST_ENTRY(conf_menu_entry) entry;
};
/*! Conference menu structure. Contains a list
* of DTMF sequences coupled with the actions those
* sequences invoke.*/
struct conf_menu {
char name[128];
int delme;
AST_LIST_HEAD_NOLOCK(, conf_menu_entry) entries;
};
struct user_profile {
char name[128];
char pin[MAX_PIN];
char moh_class[128];
unsigned int flags;
unsigned int announce_user_count_all_after;
/*! The time in ms of talking before a user is considered to be talking by the dsp. */
unsigned int talking_threshold;
/*! The time in ms of silence before a user is considered to be silent by the dsp. */
unsigned int silence_threshold;
int delme;
};
enum conf_sounds {
CONF_SOUND_HAS_JOINED,
CONF_SOUND_HAS_LEFT,
CONF_SOUND_KICKED,
CONF_SOUND_MUTED,
CONF_SOUND_UNMUTED,
CONF_SOUND_ONLY_ONE,
CONF_SOUND_THERE_ARE,
CONF_SOUND_OTHER_IN_PARTY,
CONF_SOUND_PLACE_IN_CONF,
CONF_SOUND_WAIT_FOR_LEADER,
CONF_SOUND_LEADER_HAS_LEFT,
CONF_SOUND_GET_PIN,
CONF_SOUND_INVALID_PIN,
CONF_SOUND_ONLY_PERSON,
CONF_SOUND_LOCKED,
CONF_SOUND_LOCKED_NOW,
CONF_SOUND_UNLOCKED_NOW,
CONF_SOUND_ERROR_MENU,
CONF_SOUND_JOIN,
CONF_SOUND_LEAVE,
};
struct bridge_profile_sounds {
AST_DECLARE_STRING_FIELDS(
AST_STRING_FIELD(hasjoin);
AST_STRING_FIELD(hasleft);
AST_STRING_FIELD(kicked);
AST_STRING_FIELD(muted);
AST_STRING_FIELD(unmuted);
AST_STRING_FIELD(onlyone);
AST_STRING_FIELD(thereare);
AST_STRING_FIELD(otherinparty);
AST_STRING_FIELD(placeintoconf);
AST_STRING_FIELD(waitforleader);
AST_STRING_FIELD(leaderhasleft);
AST_STRING_FIELD(getpin);
AST_STRING_FIELD(invalidpin);
AST_STRING_FIELD(onlyperson);
AST_STRING_FIELD(locked);
AST_STRING_FIELD(lockednow);
AST_STRING_FIELD(unlockednow);
AST_STRING_FIELD(errormenu);
AST_STRING_FIELD(leave);
AST_STRING_FIELD(join);
);
};
struct bridge_profile {
char name[64];
char rec_file[PATH_MAX];
unsigned int flags;
unsigned int max_members; /*!< The maximum number of participants allowed in the conference */
unsigned int internal_sample_rate; /*!< The internal sample rate of the bridge. 0 when set to auto adjust mode. */
unsigned int mix_interval; /*!< The internal mixing interval used by the bridge. When set to 0 the bridgewill use a default interval. */
struct bridge_profile_sounds *sounds;
int delme;
};
/*! \brief The structure that represents a conference bridge */
struct conference_bridge {
char name[MAX_CONF_NAME]; /*!< Name of the conference bridge */
struct ast_bridge *bridge; /*!< Bridge structure doing the mixing */
struct bridge_profile b_profile; /*!< The Bridge Configuration Profile */
unsigned int users; /*!< Number of users present */
unsigned int markedusers; /*!< Number of marked users present */
unsigned int locked:1; /*!< Is this conference bridge locked? */
struct ast_channel *playback_chan; /*!< Channel used for playback into the conference bridge */
struct ast_channel *record_chan; /*!< Channel used for recording the conference */
pthread_t record_thread; /*!< The thread the recording chan lives in */
ast_mutex_t playback_lock; /*!< Lock used for playback channel */
AST_LIST_HEAD_NOLOCK(, conference_bridge_user) users_list; /*!< List of users participating in the conference bridge */
};
/*! \brief The structure that represents a conference bridge user */
struct conference_bridge_user {
struct conference_bridge *conference_bridge; /*!< Conference bridge they are participating in */
struct bridge_profile b_profile; /*!< The Bridge Configuration Profile */
struct user_profile u_profile; /*!< The User Configuration Profile */
char menu_name[64]; /*!< The name of the DTMF menu assigned to this user */
char name_rec_location[PATH_MAX]; /*!< Location of the User's name recorded file if it exists */
struct ast_channel *chan; /*!< Asterisk channel participating */
struct ast_bridge_features features; /*!< Bridge features structure */
struct ast_bridge_tech_optimizations tech_args; /*!< Bridge technology optimizations for talk detection */
unsigned int kicked:1; /*!< User has been kicked from the conference */
AST_LIST_ENTRY(conference_bridge_user) list; /*!< Linked list information */
};
/*! \brief load confbridge.conf file */
int conf_load_config(int reload);
/*! \brief destroy the information loaded from the confbridge.conf file*/
void conf_destroy_config(void);
/*!
* \brief find a user profile given a user profile's name and store
* that profile in result structure.
*
* \details This function first attempts to find any custom user
* profile that might exist on a channel datastore, if that doesn't
* exist it looks up the provided user profile name, if that doesn't
* exist either the default_user profile is used.
* \retval user profile on success
* \retval NULL on failure
*/
const struct user_profile *conf_find_user_profile(struct ast_channel *chan, const char *user_profile_name, struct user_profile *result);
/*!
* \brief Find a bridge profile
*
* \details Any bridge profile found using this function must be
* destroyed using conf_bridge_profile_destroy. This function first
* attempts to find any custom bridge profile that might exist on
* a channel datastore, if that doesn't exist it looks up the
* provided bridge profile name, if that doesn't exist either
* the default_bridge profile is used.
*
* \retval Bridge profile on success
* \retval NULL on failure
*/
const struct bridge_profile *conf_find_bridge_profile(struct ast_channel *chan, const char *bridge_profile_name, struct bridge_profile *result);
/*!
* \brief Destroy a bridge profile found by 'conf_find_bridge_profile'
*/
void conf_bridge_profile_destroy(struct bridge_profile *b_profile);
/*!
* \brief copies a bridge profile
* \note conf_bridge_profile_destroy must be called on the dst structure
*/
void conf_bridge_profile_copy(struct bridge_profile *dst, struct bridge_profile *src);
/*!
* \brief Set a DTMF menu to a conference user by menu name.
*
* \retval 0 on success, menu was found and set
* \retval -1 on error, menu was not found
*/
int conf_set_menu_to_user(const char *menu_name, struct conference_bridge_user *conference_bridge_user);
/*!
* \brief Finds a menu_entry in a menu structure matched by DTMF sequence.
*
* \note the menu entry found must be destroyed using conf_menu_entry_destroy()
*
* \retval 1 success, entry is found and stored in result
* \retval 0 failure, no entry found for given DTMF sequence
*/
int conf_find_menu_entry_by_sequence(const char *dtmf_sequence, struct conf_menu *menu, struct conf_menu_entry *result);
/*!
* \brief Destroys and frees all the actions stored in a menu_entry structure
*/
void conf_menu_entry_destroy(struct conf_menu_entry *menu_entry);
/*!
* \brief Once a DTMF sequence matches a sequence in the user's DTMF menu, this function will get
* called to perform the menu action.
*
* \param bridge_channel, Bridged channel this is involving
* \param conference_bridge_user, the conference user to perform the action on.
* \param menu_entry, the menu entry that invoked this callback to occur.
* \param menu, an AO2 referenced pointer to the entire menu structure the menu_entry
* derived from.
*
* \note The menu_entry is a deep copy of the entry found in the menu structure. This allows
* for the menu_entry to be accessed without requiring the menu lock. If the menu must
* be accessed, the menu lock must be held. Reference counting of the menu structure is
* handled outside of the scope of this function.
*
* \retval 0 success
* \retval -1 failure
*/
int conf_handle_dtmf(
struct ast_bridge_channel *bridge_channel,
struct conference_bridge_user *conference_bridge_user,
struct conf_menu_entry *menu_entry,
struct conf_menu *menu);
/*! \brief Looks to see if sound file is stored in bridge profile sounds, if not
* default sound is provided.*/
const char *conf_get_sound(enum conf_sounds sound, struct bridge_profile_sounds *custom_sounds);
int func_confbridge_helper(struct ast_channel *chan, const char *cmd, char *data, const char *value);
#endif

View File

@ -198,12 +198,12 @@ static int feature_attended_transfer(struct ast_bridge *bridge, struct ast_bridg
ast_bridge_features_enable(&caller_features, AST_BRIDGE_BUILTIN_HANGUP,
(attended_transfer && !ast_strlen_zero(attended_transfer->complete) ? attended_transfer->complete : "*1"), NULL);
ast_bridge_features_hook(&caller_features, (attended_transfer && !ast_strlen_zero(attended_transfer->threeway) ? attended_transfer->threeway : "*2"),
attended_threeway_transfer, NULL);
attended_threeway_transfer, NULL, NULL);
ast_bridge_features_hook(&caller_features, (attended_transfer && !ast_strlen_zero(attended_transfer->abort) ? attended_transfer->abort : "*3"),
attended_abort_transfer, NULL);
attended_abort_transfer, NULL, NULL);
/* But for the caller we want to join the bridge in a blocking fashion so we don't spin around in this function doing nothing while waiting */
attended_bridge_result = ast_bridge_join(attended_bridge, bridge_channel->chan, NULL, &caller_features);
attended_bridge_result = ast_bridge_join(attended_bridge, bridge_channel->chan, NULL, &caller_features, NULL);
/* Since the above returned the caller features structure is of no more use */
ast_bridge_features_cleanup(&caller_features);

View File

@ -1,9 +1,10 @@
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2007, Digium, Inc.
* Copyright (C) 2011, Digium, Inc.
*
* Joshua Colp <jcolp@digium.com>
* David Vossel <dvossel@digium.com>
*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
@ -21,12 +22,9 @@
* \brief Multi-party software based channel mixing
*
* \author Joshua Colp <jcolp@digium.com>
* \author David Vossel <dvossel@digium.com>
*
* \ingroup bridges
*
* \todo This bridge operates in 8 kHz mode unless a define is uncommented.
* This needs to be improved so the bridge moves between the dominant codec as needed depending
* on channels present in the bridge and transcoding capabilities.
*/
#include "asterisk.h"
@ -51,20 +49,26 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
#include "asterisk/slinfactory.h"
#include "asterisk/astobj2.h"
#include "asterisk/timing.h"
#include "asterisk/translate.h"
#define MAX_DATALEN 3840
#define MAX_DATALEN 8096
/*! \brief Interval at which mixing will take place. Valid options are 10, 20, and 40. */
#define SOFTMIX_INTERVAL 20
#define DEFAULT_SOFTMIX_INTERVAL 20
/*! \brief Size of the buffer used for sample manipulation */
#define SOFTMIX_DATALEN(rate) ((rate/50) * (SOFTMIX_INTERVAL / 10))
#define SOFTMIX_DATALEN(rate, interval) ((rate/50) * (interval / 10))
/*! \brief Number of samples we are dealing with */
#define SOFTMIX_SAMPLES(rate) (SOFTMIX_DATALEN(rate) / 2)
#define SOFTMIX_SAMPLES(rate, interval) (SOFTMIX_DATALEN(rate, interval) / 2)
/*! \brief Define used to turn on 16 kHz audio support */
/* #define SOFTMIX_16_SUPPORT */
/*! \brief Number of mixing iterations to perform between gathering statistics. */
#define SOFTMIX_STAT_INTERVAL 100
/* This is the threshold in ms at which a channel's own audio will stop getting
* mixed out its own write audio stream because it is not talking. */
#define DEFAULT_SOFTMIX_SILENCE_THRESHOLD 2500
#define DEFAULT_SOFTMIX_TALKING_THRESHOLD 160
/*! \brief Structure which contains per-channel mixing information */
struct softmix_channel {
@ -73,7 +77,14 @@ struct softmix_channel {
/*! Factory which contains audio read in from the channel */
struct ast_slinfactory factory;
/*! Frame that contains mixed audio to be written out to the channel */
struct ast_frame frame;
struct ast_frame write_frame;
/*! Frame that contains mixed audio read from the channel */
struct ast_frame read_frame;
/*! DSP for detecting silence */
struct ast_dsp *dsp;
/*! Bit used to indicate if a channel is talking or not. This affects how
* the channel's audio is mixed back to it. */
int talking:1;
/*! Bit used to indicate that the channel provided audio for this mixing interval */
int have_audio:1;
/*! Bit used to indicate that a frame is available to be written out to the channel */
@ -87,66 +98,268 @@ struct softmix_channel {
struct softmix_bridge_data {
struct ast_timer *timer;
unsigned int internal_rate;
unsigned int internal_mixing_interval;
};
struct softmix_stats {
/*! Each index represents a sample rate used above the internal rate. */
unsigned int sample_rates[16];
/*! Each index represents the number of channels using the same index in the sample_rates array. */
unsigned int num_channels[16];
/*! the number of channels above the internal sample rate */
unsigned int num_above_internal_rate;
/*! the number of channels at the internal sample rate */
unsigned int num_at_internal_rate;
/*! the absolute highest sample rate supported by any channel in the bridge */
unsigned int highest_supported_rate;
/*! Is the sample rate locked by the bridge, if so what is that rate.*/
unsigned int locked_rate;
};
struct softmix_mixing_array {
int max_num_entries;
int used_entries;
int16_t **buffers;
};
struct softmix_translate_helper_entry {
int num_times_requested; /*!< Once this entry is no longer requested, free the trans_pvt
and re-init if it was usable. */
struct ast_format dst_format; /*!< The destination format for this helper */
struct ast_trans_pvt *trans_pvt; /*!< the translator for this slot. */
struct ast_frame *out_frame; /*!< The output frame from the last translation */
AST_LIST_ENTRY(softmix_translate_helper_entry) entry;
};
struct softmix_translate_helper {
struct ast_format slin_src; /*!< the source format expected for all the translators */
AST_LIST_HEAD_NOLOCK(, softmix_translate_helper_entry) entries;
};
static struct softmix_translate_helper_entry *softmix_translate_helper_entry_alloc(struct ast_format *dst)
{
struct softmix_translate_helper_entry *entry;
if (!(entry = ast_calloc(1, sizeof(*entry)))) {
return NULL;
}
ast_format_copy(&entry->dst_format, dst);
return entry;
}
static void *softmix_translate_helper_free_entry(struct softmix_translate_helper_entry *entry)
{
if (entry->trans_pvt) {
ast_translator_free_path(entry->trans_pvt);
}
if (entry->out_frame) {
ast_frfree(entry->out_frame);
}
ast_free(entry);
return NULL;
}
static void softmix_translate_helper_init(struct softmix_translate_helper *trans_helper, unsigned int sample_rate)
{
memset(trans_helper, 0, sizeof(*trans_helper));
ast_format_set(&trans_helper->slin_src, ast_format_slin_by_rate(sample_rate), 0);
}
static void softmix_translate_helper_destroy(struct softmix_translate_helper *trans_helper)
{
struct softmix_translate_helper_entry *entry;
while ((entry = AST_LIST_REMOVE_HEAD(&trans_helper->entries, entry))) {
softmix_translate_helper_free_entry(entry);
}
}
static void softmix_translate_helper_change_rate(struct softmix_translate_helper *trans_helper, unsigned int sample_rate)
{
struct softmix_translate_helper_entry *entry;
ast_format_set(&trans_helper->slin_src, ast_format_slin_by_rate(sample_rate), 0);
AST_LIST_TRAVERSE_SAFE_BEGIN(&trans_helper->entries, entry, entry) {
if (entry->trans_pvt) {
ast_translator_free_path(entry->trans_pvt);
if (!(entry->trans_pvt = ast_translator_build_path(&entry->dst_format, &trans_helper->slin_src))) {
AST_LIST_REMOVE_CURRENT(entry);
entry = softmix_translate_helper_free_entry(entry);
}
}
}
AST_LIST_TRAVERSE_SAFE_END;
}
/*!
* \internal
* \brief Get the next available audio on the softmix channel's read stream
* and determine if it should be mixed out or not on the write stream.
*
* \retval pointer to buffer containing the exact number of samples requested on success.
* \retval NULL if no samples are present
*/
static int16_t *softmix_process_read_audio(struct softmix_channel *sc, unsigned int num_samples)
{
if ((ast_slinfactory_available(&sc->factory) >= num_samples) &&
ast_slinfactory_read(&sc->factory, sc->our_buf, num_samples)) {
sc->have_audio = 1;
return sc->our_buf;
}
sc->have_audio = 0;
return NULL;
}
/*!
* \internal
* \brief Process a softmix channel's write audio
*
* \details This function will remove the channel's talking from its own audio if present and
* possibly even do the channel's write translation for it depending on how many other
* channels use the same write format.
*/
static void softmix_process_write_audio(struct softmix_translate_helper *trans_helper,
struct ast_format *raw_write_fmt,
struct softmix_channel *sc)
{
struct softmix_translate_helper_entry *entry = NULL;
int i;
/* If we provided audio that was not determined to be silence,
* then take it out while in slinear format. */
if (sc->have_audio && sc->talking) {
for (i = 0; i < sc->write_frame.samples; i++) {
ast_slinear_saturated_subtract(&sc->final_buf[i], &sc->our_buf[i]);
}
/* do not do any special write translate optimization if we had to make
* a special mix for them to remove their own audio. */
return;
}
AST_LIST_TRAVERSE(&trans_helper->entries, entry, entry) {
if (ast_format_cmp(&entry->dst_format, raw_write_fmt) == AST_FORMAT_CMP_EQUAL) {
entry->num_times_requested++;
} else {
continue;
}
if (!entry->trans_pvt && (entry->num_times_requested > 1)) {
entry->trans_pvt = ast_translator_build_path(&entry->dst_format, &trans_helper->slin_src);
}
if (entry->trans_pvt && !entry->out_frame) {
entry->out_frame = ast_translate(entry->trans_pvt, &sc->write_frame, 0);
}
if (entry->out_frame && (entry->out_frame->datalen < MAX_DATALEN)) {
ast_format_copy(&sc->write_frame.subclass.format, &entry->out_frame->subclass.format);
memcpy(sc->final_buf, entry->out_frame->data.ptr, entry->out_frame->datalen);
sc->write_frame.datalen = entry->out_frame->datalen;
sc->write_frame.samples = entry->out_frame->samples;
}
break;
}
/* add new entry into list if this format destination was not matched. */
if (!entry && (entry = softmix_translate_helper_entry_alloc(raw_write_fmt))) {
AST_LIST_INSERT_HEAD(&trans_helper->entries, entry, entry);
}
}
static void softmix_translate_helper_cleanup(struct softmix_translate_helper *trans_helper)
{
struct softmix_translate_helper_entry *entry = NULL;
AST_LIST_TRAVERSE(&trans_helper->entries, entry, entry) {
if (entry->out_frame) {
ast_frfree(entry->out_frame);
entry->out_frame = NULL;
}
entry->num_times_requested = 0;
}
}
static void softmix_bridge_data_destroy(void *obj)
{
struct softmix_bridge_data *softmix_data = obj;
ast_timer_close(softmix_data->timer);
}
/*! \brief Function called when a bridge is created */
static int softmix_bridge_create(struct ast_bridge *bridge)
{
struct softmix_bridge_data *bridge_data;
struct softmix_bridge_data *softmix_data;
if (!(bridge_data = ast_calloc(1, sizeof(*bridge_data)))) {
if (!(softmix_data = ao2_alloc(sizeof(*softmix_data), softmix_bridge_data_destroy))) {
return -1;
}
if (!(bridge_data->timer = ast_timer_open())) {
ast_free(bridge_data);
if (!(softmix_data->timer = ast_timer_open())) {
ao2_ref(softmix_data, -1);
return -1;
}
/* start at 8khz, let it grow from there */
bridge_data->internal_rate = 8000;
softmix_data->internal_rate = 8000;
softmix_data->internal_mixing_interval = DEFAULT_SOFTMIX_INTERVAL;
bridge->bridge_pvt = bridge_data;
bridge->bridge_pvt = softmix_data;
return 0;
}
/*! \brief Function called when a bridge is destroyed */
static int softmix_bridge_destroy(struct ast_bridge *bridge)
{
struct softmix_bridge_data *bridge_data = bridge->bridge_pvt;
struct softmix_bridge_data *softmix_data = bridge->bridge_pvt;
if (!bridge->bridge_pvt) {
return -1;
}
ast_timer_close(bridge_data->timer);
ast_free(bridge_data);
ao2_ref(softmix_data, -1);
bridge->bridge_pvt = NULL;
return 0;
}
static void set_softmix_bridge_data(int rate, struct ast_bridge_channel *bridge_channel, int reset)
static void set_softmix_bridge_data(int rate, int interval, struct ast_bridge_channel *bridge_channel, int reset)
{
struct softmix_channel *sc = bridge_channel->bridge_pvt;
unsigned int channel_read_rate = ast_format_rate(&bridge_channel->chan->rawreadformat);
ast_mutex_lock(&sc->lock);
if (reset) {
ast_slinfactory_destroy(&sc->factory);
ast_dsp_free(sc->dsp);
}
/* Setup frame parameters */
sc->frame.frametype = AST_FRAME_VOICE;
/* Setup read/write frame parameters */
sc->write_frame.frametype = AST_FRAME_VOICE;
ast_format_set(&sc->write_frame.subclass.format, ast_format_slin_by_rate(rate), 0);
sc->write_frame.data.ptr = sc->final_buf;
sc->write_frame.datalen = SOFTMIX_DATALEN(rate, interval);
sc->write_frame.samples = SOFTMIX_SAMPLES(rate, interval);
ast_format_set(&sc->frame.subclass.format, ast_format_slin_by_rate(rate), 0);
sc->frame.data.ptr = sc->final_buf;
sc->frame.datalen = SOFTMIX_DATALEN(rate);
sc->frame.samples = SOFTMIX_SAMPLES(rate);
sc->read_frame.frametype = AST_FRAME_VOICE;
ast_format_set(&sc->read_frame.subclass.format, ast_format_slin_by_rate(channel_read_rate), 0);
sc->read_frame.data.ptr = sc->our_buf;
sc->read_frame.datalen = SOFTMIX_DATALEN(channel_read_rate, interval);
sc->read_frame.samples = SOFTMIX_SAMPLES(channel_read_rate, interval);
/* Setup smoother */
ast_slinfactory_init_with_format(&sc->factory, &sc->frame.subclass.format);
ast_slinfactory_init_with_format(&sc->factory, &sc->write_frame.subclass.format);
ast_set_read_format(bridge_channel->chan, &sc->frame.subclass.format);
ast_set_write_format(bridge_channel->chan, &sc->frame.subclass.format);
/* set new read and write formats on channel. */
ast_set_read_format(bridge_channel->chan, &sc->read_frame.subclass.format);
ast_set_write_format(bridge_channel->chan, &sc->write_frame.subclass.format);
/* set up new DSP. This is on the read side only right before the read frame enters the smoother. */
sc->dsp = ast_dsp_new_with_rate(channel_read_rate);
/* we want to aggressively detect silence to avoid feedback */
if (bridge_channel->tech_args.talking_threshold) {
ast_dsp_set_threshold(sc->dsp, bridge_channel->tech_args.talking_threshold);
} else {
ast_dsp_set_threshold(sc->dsp, DEFAULT_SOFTMIX_TALKING_THRESHOLD);
}
ast_mutex_unlock(&sc->lock);
}
/*! \brief Function called when a channel is joined into the bridge */
static int softmix_bridge_join(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
{
struct softmix_channel *sc = NULL;
struct softmix_bridge_data *bridge_data = bridge->bridge_pvt;
struct softmix_bridge_data *softmix_data = bridge->bridge_pvt;
/* Create a new softmix_channel structure and allocate various things on it */
if (!(sc = ast_calloc(1, sizeof(*sc)))) {
@ -159,7 +372,9 @@ static int softmix_bridge_join(struct ast_bridge *bridge, struct ast_bridge_chan
/* Can't forget to record our pvt structure within the bridged channel structure */
bridge_channel->bridge_pvt = sc;
set_softmix_bridge_data(bridge_data->internal_rate, bridge_channel, 0);
set_softmix_bridge_data(softmix_data->internal_rate,
softmix_data->internal_mixing_interval ? softmix_data->internal_mixing_interval : DEFAULT_SOFTMIX_INTERVAL,
bridge_channel, 0);
return 0;
}
@ -169,44 +384,102 @@ static int softmix_bridge_leave(struct ast_bridge *bridge, struct ast_bridge_cha
{
struct softmix_channel *sc = bridge_channel->bridge_pvt;
if (!(bridge_channel->bridge_pvt)) {
return 0;
}
bridge_channel->bridge_pvt = NULL;
/* Drop mutex lock */
ast_mutex_destroy(&sc->lock);
/* Drop the factory */
ast_slinfactory_destroy(&sc->factory);
/* Drop the DSP */
ast_dsp_free(sc->dsp);
/* Eep! drop ourselves */
ast_free(sc);
return 0;
}
/*!
* \internal
* \brief If the bridging core passes DTMF to us, then they want it to be distributed out to all memebers. Do that here.
*/
static void softmix_pass_dtmf(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
{
struct ast_bridge_channel *tmp;
AST_LIST_TRAVERSE(&bridge->channels, tmp, entry) {
if (tmp == bridge_channel) {
continue;
}
ast_write(tmp->chan, frame);
}
}
/*! \brief Function called when a channel writes a frame into the bridge */
static enum ast_bridge_write_result softmix_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
{
struct softmix_channel *sc = bridge_channel->bridge_pvt;
struct softmix_bridge_data *softmix_data = bridge->bridge_pvt;
int totalsilence = 0;
int silence_threshold = bridge_channel->tech_args.silence_threshold ?
bridge_channel->tech_args.silence_threshold :
DEFAULT_SOFTMIX_SILENCE_THRESHOLD;
char update_talking = -1; /* if this is set to 0 or 1, tell the bridge that the channel has started or stopped talking. */
/* Only accept audio frames, all others are unsupported */
if (frame->frametype != AST_FRAME_VOICE) {
if (frame->frametype == AST_FRAME_DTMF_END || frame->frametype == AST_FRAME_DTMF_BEGIN) {
softmix_pass_dtmf(bridge, bridge_channel, frame);
return AST_BRIDGE_WRITE_SUCCESS;
} else if (frame->frametype != AST_FRAME_VOICE) {
return AST_BRIDGE_WRITE_UNSUPPORTED;
}
ast_mutex_lock(&sc->lock);
/* If a frame was provided add it to the smoother */
if (frame->frametype == AST_FRAME_VOICE && ast_format_is_slinear(&frame->subclass.format)) {
ast_dsp_silence(sc->dsp, frame, &totalsilence);
if (totalsilence < silence_threshold) {
if (!sc->talking) {
update_talking = 1;
}
sc->talking = 1; /* tell the write process we have audio to be mixed out */
} else {
if (sc->talking) {
update_talking = 0;
}
sc->talking = 0;
}
/* Before adding audio in, make sure we haven't fallen behind. If audio has fallen
* behind 4 times the amount of samples mixed on every iteration of the mixer, Re-sync
* the audio by flushing the buffer before adding new audio in. */
if (ast_slinfactory_available(&sc->factory) > (4 * SOFTMIX_SAMPLES(softmix_data->internal_rate, softmix_data->internal_mixing_interval))) {
ast_slinfactory_flush(&sc->factory);
}
/* If a frame was provided add it to the smoother, unless drop silence is enabled and this frame
* is not determined to be talking. */
if (!(bridge_channel->tech_args.drop_silence && !sc->talking) &&
(frame->frametype == AST_FRAME_VOICE && ast_format_is_slinear(&frame->subclass.format))) {
ast_slinfactory_feed(&sc->factory, frame);
}
/* If a frame is ready to be written out, do so */
if (sc->have_frame) {
ast_write(bridge_channel->chan, &sc->frame);
ast_write(bridge_channel->chan, &sc->write_frame);
sc->have_frame = 0;
}
/* Alllll done */
ast_mutex_unlock(&sc->lock);
if (update_talking != -1) {
ast_bridge_notify_talking(bridge, bridge_channel, update_talking);
}
return AST_BRIDGE_WRITE_SUCCESS;
}
@ -218,7 +491,7 @@ static int softmix_bridge_poke(struct ast_bridge *bridge, struct ast_bridge_chan
ast_mutex_lock(&sc->lock);
if (sc->have_frame) {
ast_write(bridge_channel->chan, &sc->frame);
ast_write(bridge_channel->chan, &sc->write_frame);
sc->have_frame = 0;
}
@ -227,167 +500,306 @@ static int softmix_bridge_poke(struct ast_bridge *bridge, struct ast_bridge_chan
return 0;
}
static void gather_softmix_stats(struct softmix_stats *stats,
const struct softmix_bridge_data *softmix_data,
struct ast_bridge_channel *bridge_channel)
{
int channel_native_rate;
int i;
/* Gather stats about channel sample rates. */
channel_native_rate = MAX(ast_format_rate(&bridge_channel->chan->rawwriteformat),
ast_format_rate(&bridge_channel->chan->rawreadformat));
if (channel_native_rate > stats->highest_supported_rate) {
stats->highest_supported_rate = channel_native_rate;
}
if (channel_native_rate > softmix_data->internal_rate) {
for (i = 0; i < ARRAY_LEN(stats->sample_rates); i++) {
if (stats->sample_rates[i] == channel_native_rate) {
stats->num_channels[i]++;
break;
} else if (!stats->sample_rates[i]) {
stats->sample_rates[i] = channel_native_rate;
stats->num_channels[i]++;
break;
}
}
stats->num_above_internal_rate++;
} else if (channel_native_rate == softmix_data->internal_rate) {
stats->num_at_internal_rate++;
}
}
/*!
* \internal
* \brief Analyse mixing statistics and change bridges internal rate
* if necessary.
*
* \retval 0, no changes to internal rate
* \ratval 1, internal rate was changed, update all the channels on the next mixing iteration.
*/
static unsigned int analyse_softmix_stats(struct softmix_stats *stats, struct softmix_bridge_data *softmix_data)
{
int i;
/* Re-adjust the internal bridge sample rate if
* 1. The bridge's internal sample rate is locked in at a sample
* rate other than the current sample rate being used.
* 2. two or more channels support a higher sample rate
* 3. no channels support the current sample rate or a higher rate
*/
if (stats->locked_rate) {
/* if the rate is locked by the bridge, only update it if it differs
* from the current rate we are using. */
if (softmix_data->internal_rate != stats->locked_rate) {
softmix_data->internal_rate = stats->locked_rate;
ast_debug(1, " Bridge is locked in at sample rate %d\n", softmix_data->internal_rate);
return 1;
}
} else if (stats->num_above_internal_rate >= 2) {
/* the highest rate is just used as a starting point */
unsigned int best_rate = stats->highest_supported_rate;
int best_index = -1;
for (i = 0; i < ARRAY_LEN(stats->num_channels); i++) {
if (stats->num_channels[i]) {
break;
}
/* best_rate starts out being the first sample rate
* greater than the internal sample rate that 2 or
* more channels support. */
if (stats->num_channels[i] >= 2 && (best_index == -1)) {
best_rate = stats->sample_rates[i];
best_index = i;
/* If it has been detected that multiple rates above
* the internal rate are present, compare those rates
* to each other and pick the highest one two or more
* channels support. */
} else if (((best_index != -1) &&
(stats->num_channels[i] >= 2) &&
(stats->sample_rates[best_index] < stats->sample_rates[i]))) {
best_rate = stats->sample_rates[i];
best_index = i;
/* It is possible that multiple channels exist with native sample
* rates above the internal sample rate, but none of those channels
* have the same rate in common. In this case, the lowest sample
* rate among those channels is picked. Over time as additional
* statistic runs are made the internal sample rate number will
* adjust to the most optimal sample rate, but it may take multiple
* iterations. */
} else if (best_index == -1) {
best_rate = MIN(best_rate, stats->sample_rates[i]);
}
}
ast_debug(1, " Bridge changed from %d To %d\n", softmix_data->internal_rate, best_rate);
softmix_data->internal_rate = best_rate;
return 1;
} else if (!stats->num_at_internal_rate && !stats->num_above_internal_rate) {
/* In this case, the highest supported rate is actually lower than the internal rate */
softmix_data->internal_rate = stats->highest_supported_rate;
ast_debug(1, " Bridge changed from %d to %d\n", softmix_data->internal_rate, stats->highest_supported_rate);
return 1;
}
return 0;
}
static int softmix_mixing_array_init(struct softmix_mixing_array *mixing_array, unsigned int starting_num_entries)
{
memset(mixing_array, 0, sizeof(*mixing_array));
mixing_array->max_num_entries = starting_num_entries;
if (!(mixing_array->buffers = ast_calloc(mixing_array->max_num_entries, sizeof(int16_t *)))) {
ast_log(LOG_NOTICE, "Failed to allocate softmix mixing structure. \n");
return -1;
}
return 0;
}
static void softmix_mixing_array_destroy(struct softmix_mixing_array *mixing_array)
{
ast_free(mixing_array->buffers);
}
static int softmix_mixing_array_grow(struct softmix_mixing_array *mixing_array, unsigned int num_entries)
{
int16_t **tmp;
/* give it some room to grow since memory is cheap but allocations can be expensive */
mixing_array->max_num_entries = num_entries;
if (!(tmp = ast_realloc(mixing_array->buffers, (mixing_array->max_num_entries * sizeof(int16_t *))))) {
ast_log(LOG_NOTICE, "Failed to re-allocate softmix mixing structure. \n");
return -1;
}
mixing_array->buffers = tmp;
return 0;
}
/*! \brief Function which acts as the mixing thread */
static int softmix_bridge_thread(struct ast_bridge *bridge)
{
struct {
/*! Each index represents a sample rate used above the internal rate. */
unsigned int sample_rates[8];
/*! Each index represents the number of channels using the same index in the sample_rates array. */
unsigned int num_channels[8];
/*! the number of channels above the internal sample rate */
unsigned int num_above_internal_rate;
/*! the number of channels at the internal sample rate */
unsigned int num_at_internal_rate;
/*! the absolute highest sample rate supported by any channel in the bridge */
unsigned int highest_supported_rate;
} stats;
struct softmix_bridge_data *bridge_data = bridge->bridge_pvt;
struct ast_timer *timer = bridge_data->timer;
int timingfd = ast_timer_fd(timer);
struct softmix_stats stats = { { 0 }, };
struct softmix_mixing_array mixing_array;
struct softmix_bridge_data *softmix_data = bridge->bridge_pvt;
struct ast_timer *timer;
struct softmix_translate_helper trans_helper;
int16_t buf[MAX_DATALEN] = { 0, };
unsigned int stat_iteration_counter = 0; /* counts down, gather stats at zero and reset. */
int timingfd;
int update_all_rates = 0; /* set this when the internal sample rate has changed */
int i;
int i, x;
int res = -1;
ast_timer_set_rate(timer, (1000 / SOFTMIX_INTERVAL));
if (!(softmix_data = bridge->bridge_pvt)) {
goto softmix_cleanup;
}
ao2_ref(softmix_data, 1);
timer = softmix_data->timer;
timingfd = ast_timer_fd(timer);
softmix_translate_helper_init(&trans_helper, softmix_data->internal_rate);
ast_timer_set_rate(timer, (1000 / softmix_data->internal_mixing_interval));
/* Give the mixing array room to grow, memory is cheap but allocations are expensive. */
if (softmix_mixing_array_init(&mixing_array, bridge->num + 10)) {
ast_log(LOG_NOTICE, "Failed to allocate softmix mixing structure. \n");
goto softmix_cleanup;
}
while (!bridge->stop && !bridge->refresh && bridge->array_num) {
struct ast_bridge_channel *bridge_channel = NULL;
short buf[MAX_DATALEN] = {0, };
int timeout = -1;
enum ast_format_id cur_slin_id = ast_format_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);
/* these variables help determine if a rate change is required */
memset(&stats, 0, sizeof(stats));
stats.highest_supported_rate = 8000;
if (softmix_datalen > MAX_DATALEN) {
/* This should NEVER happen, but if it does we need to know about it. Almost
* all the memcpys used during this process depend on this assumption. Rather
* than checking this over and over again through out the code, this single
* verification is done on each iteration. */
ast_log(LOG_WARNING, "Conference mixing error, requested mixing length greater than mixing buffer.\n");
goto softmix_cleanup;
}
/* Grow the mixing array buffer as participants are added. */
if (mixing_array.max_num_entries < bridge->num && softmix_mixing_array_grow(&mixing_array, bridge->num + 5)) {
goto softmix_cleanup;
}
/* init the number of buffers stored in the mixing array to 0.
* As buffers are added for mixing, this number is incremented. */
mixing_array.used_entries = 0;
/* These variables help determine if a rate change is required */
if (!stat_iteration_counter) {
memset(&stats, 0, sizeof(stats));
stats.locked_rate = bridge->internal_sample_rate;
}
/* If the sample rate has changed, update the translator helper */
if (update_all_rates) {
softmix_translate_helper_change_rate(&trans_helper, softmix_data->internal_rate);
}
/* 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->bridge_pvt;
int channel_native_rate;
ast_mutex_lock(&sc->lock);
/* Update the sample rate to match the bridge's native sample rate if necessary. */
if (update_all_rates) {
set_softmix_bridge_data(bridge_data->internal_rate, bridge_channel, 1);
set_softmix_bridge_data(softmix_data->internal_rate, softmix_data->internal_mixing_interval, bridge_channel, 1);
}
/* If stat_iteration_counter is 0, then collect statistics during this mixing interation */
if (!stat_iteration_counter) {
gather_softmix_stats(&stats, softmix_data, bridge_channel);
}
/* if the channel is suspended, don't check for audio, but still gather stats */
if (bridge_channel->suspended) {
continue;
}
/* Try to get audio from the factory if available */
if (ast_slinfactory_available(&sc->factory) >= SOFTMIX_SAMPLES(bridge_data->internal_rate) &&
ast_slinfactory_read(&sc->factory, sc->our_buf, SOFTMIX_SAMPLES(bridge_data->internal_rate))) {
short *data1, *data2;
int i;
/* Put into the local final buffer */
for (i = 0, data1 = buf, data2 = sc->our_buf; i < SOFTMIX_DATALEN(bridge_data->internal_rate); i++, data1++, data2++)
ast_slinear_saturated_add(data1, data2);
/* Yay we have our own audio */
sc->have_audio = 1;
} else {
/* Awww we don't have audio ;( */
sc->have_audio = 0;
ast_mutex_lock(&sc->lock);
if ((mixing_array.buffers[mixing_array.used_entries] = softmix_process_read_audio(sc, softmix_samples))) {
mixing_array.used_entries++;
}
/* Gather stats about channel sample rates. */
channel_native_rate = MAX(ast_format_rate(&bridge_channel->chan->rawwriteformat),
ast_format_rate(&bridge_channel->chan->rawreadformat));
if (channel_native_rate > stats.highest_supported_rate) {
stats.highest_supported_rate = channel_native_rate;
}
if (channel_native_rate > bridge_data->internal_rate) {
for (i = 0; i < ARRAY_LEN(stats.sample_rates); i++) {
if (stats.sample_rates[i] == channel_native_rate) {
stats.num_channels[i]++;
break;
} else if (!stats.sample_rates[i]) {
stats.sample_rates[i] = channel_native_rate;
stats.num_channels[i]++;
break;
}
}
stats.num_above_internal_rate++;
} else if (channel_native_rate == bridge_data->internal_rate) {
stats.num_at_internal_rate++;
}
ast_mutex_unlock(&sc->lock);
}
/* mix it like crazy */
memset(buf, 0, softmix_datalen);
for (i = 0; i < mixing_array.used_entries; i++) {
for (x = 0; x < softmix_samples; x++) {
ast_slinear_saturated_add(buf + x, mixing_array.buffers[i] + x);
}
}
/* Next step go through removing the channel's own audio and creating a good frame... */
AST_LIST_TRAVERSE(&bridge->channels, bridge_channel, entry) {
struct softmix_channel *sc = bridge_channel->bridge_pvt;
int i = 0;
/* Copy from local final buffer to our final buffer */
memcpy(sc->final_buf, buf, sizeof(sc->final_buf));
/* If we provided audio then take it out */
if (sc->have_audio) {
for (i = 0; i < SOFTMIX_DATALEN(bridge_data->internal_rate); i++) {
ast_slinear_saturated_subtract(&sc->final_buf[i], &sc->our_buf[i]);
}
if (bridge_channel->suspended) {
continue;
}
ast_mutex_lock(&sc->lock);
/* Make SLINEAR write frame from local buffer */
if (sc->write_frame.subclass.format.id != cur_slin_id) {
ast_format_set(&sc->write_frame.subclass.format, cur_slin_id, 0);
}
sc->write_frame.datalen = softmix_datalen;
sc->write_frame.samples = softmix_samples;
memcpy(sc->final_buf, buf, softmix_datalen);
/* process the softmix channel's new write audio */
softmix_process_write_audio(&trans_helper, &bridge_channel->chan->rawwriteformat, sc);
/* The frame is now ready for use... */
sc->have_frame = 1;
ast_mutex_unlock(&sc->lock);
/* Poke bridged channel thread just in case */
pthread_kill(bridge_channel->thread, SIGURG);
}
/* Re-adjust the internal bridge sample rate if
* 1. two or more channels support a higher sample rate
* 2. no channels support the current sample rate or a higher rate
*/
if (stats.num_above_internal_rate >= 2) {
/* the highest rate is just used as a starting point */
unsigned int best_rate = stats.highest_supported_rate;
int best_index = -1;
/* 1. pick the best sample rate two or more channels support
* 2. if two or more channels do not support the same rate, pick the
* lowest sample rate that is still above the internal rate. */
for (i = 0; ((i < ARRAY_LEN(stats.num_channels)) && stats.num_channels[i]); i++) {
if ((stats.num_channels[i] >= 2 && (best_index == -1)) ||
((best_index != -1) &&
(stats.num_channels[i] >= 2) &&
(stats.sample_rates[best_index] < stats.sample_rates[i]))) {
best_rate = stats.sample_rates[i];
best_index = i;
} else if (best_index == -1) {
best_rate = MIN(best_rate, stats.sample_rates[i]);
}
}
ast_debug(1, " Bridge changed from %d To %d\n", bridge_data->internal_rate, best_rate);
bridge_data->internal_rate = best_rate;
update_all_rates = 1;
} else if (!stats.num_at_internal_rate && !stats.num_above_internal_rate) {
update_all_rates = 1;
/* in this case, the highest supported rate is actually lower than the internal rate */
bridge_data->internal_rate = stats.highest_supported_rate;
ast_debug(1, " Bridge changed from %d to %d\n", bridge_data->internal_rate, stats.highest_supported_rate);
update_all_rates = 1;
} else {
update_all_rates = 0;
update_all_rates = 0;
if (!stat_iteration_counter) {
update_all_rates = analyse_softmix_stats(&stats, softmix_data);
stat_iteration_counter = SOFTMIX_STAT_INTERVAL;
}
stat_iteration_counter--;
ao2_unlock(bridge);
/* cleanup any translation frame data from the previous mixing iteration. */
softmix_translate_helper_cleanup(&trans_helper);
/* Wait for the timing source to tell us to wake up and get things done */
ast_waitfor_n_fd(&timingfd, 1, &timeout, NULL);
ast_timer_ack(timer, 1);
ao2_lock(bridge);
/* make sure to detect mixing interval changes if they occur. */
if (bridge->internal_mixing_interval && (bridge->internal_mixing_interval != softmix_data->internal_mixing_interval)) {
softmix_data->internal_mixing_interval = bridge->internal_mixing_interval;
ast_timer_set_rate(timer, (1000 / softmix_data->internal_mixing_interval));
update_all_rates = 1; /* if the interval changes, the rates must be adjusted as well just to be notified new interval.*/
}
}
return 0;
res = 0;
softmix_cleanup:
softmix_translate_helper_destroy(&trans_helper);
softmix_mixing_array_destroy(&mixing_array);
if (softmix_data) {
ao2_ref(softmix_data, -1);
}
return res;
}
static struct ast_bridge_technology softmix_bridge = {
.name = "softmix",
.capabilities = AST_BRIDGE_CAPABILITY_MULTIMIX | AST_BRIDGE_CAPABILITY_THREAD | AST_BRIDGE_CAPABILITY_MULTITHREADED,
.capabilities = AST_BRIDGE_CAPABILITY_MULTIMIX | AST_BRIDGE_CAPABILITY_THREAD | AST_BRIDGE_CAPABILITY_MULTITHREADED | AST_BRIDGE_CAPABILITY_OPTIMIZE,
.preference = AST_BRIDGE_PREFERENCE_LOW,
.create = softmix_bridge_create,
.destroy = softmix_bridge_destroy,
@ -410,11 +822,7 @@ static int load_module(void)
if (!(softmix_bridge.format_capabilities = ast_format_cap_alloc())) {
return AST_MODULE_LOAD_DECLINE;
}
#ifdef SOFTMIX_16_SUPPORT
ast_format_cap_add(softmix_bridge.format_capabilities, ast_format_set(&tmp, AST_FORMAT_SLINEAR16, 0));
#else
ast_format_cap_add(softmix_bridge.format_capabilities, ast_format_set(&tmp, AST_FORMAT_SLINEAR, 0));
#endif
return ast_bridge_technology_register(&softmix_bridge);
}

View File

@ -0,0 +1,302 @@
[general]
; The general section of this config
; is not currently used, but reserved
; for future use.
;
; --- Default Information ---
; The default_user and default_bridge sections are applied
; automatically to all ConfBridge instances invoked without
; a user, or bridge argument. No menu is applied by default.
;
; --- ConfBridge User Profile Options ---
[default_user]
type=user
;admin=yes ; Sets if the user is an admin or not. Off by default.
;marked=yes ; Sets if this is a marked user or not. Off by default.
;startmuted=yes; Sets if all users should start out muted. Off by default
;music_on_hold_when_empty=yes ; Sets whether MOH should be played when only
; one person is in the conference or when the
; the user is waiting on a marked user to enter
; the conference. Off by default.
;music_on_hold_class=default ; The MOH class to use for this user.
;quiet=yes ; When enabled enter/leave prompts and user intros are not played.
; There are some prompts, such as the prompt to enter a PIN number,
; that must be played regardless of what this option is set to.
; Off by default
;announce_user_count=yes ; Sets if the number of users should be announced to the
; caller. Off by default.
;announce_user_count_all=yes ; Sets if the number of users should be announced to
; all the other users in the conference when someone joins.
; This option can be either set to 'yes' or a number.
; When set to a number, the announcement will only occur
; once the user count is above the specified number.
;announce_only_user=yes ; Sets if the only user announcement should be played
; when a channel enters a empty conference. On by default.
;wait_marked=yes ; Sets if the user must wait for a marked user to enter before
; joining the conference. Off by default.
;end_marked=yes ; This option will kick every user with this option set in their
; user profile after the last Marked user exists the conference.
;dsp_drop_silence=yes ; This option drops what Asterisk detects as silence from
; entering into the bridge. Enabling this option will drastically
; improve performance and help remove the buildup of background
; noise from the conference. Highly recommended for large conferences
; due to its performance enhancements.
;dsp_talking_threshold=128 ; The time in milliseconds of sound above what the dsp has
; established as base line silence for a user before a user
; is considered to be talking. This value affects several
; operations and should not be changed unless the impact on
; call quality is fully understood.
;
; What this value affects internally:
;
; 1. Audio is only mixed out of a user's incoming audio stream
; if talking is detected. If this value is set too
; loose the user will hear themselves briefly each
; time they begin talking until the dsp has time to
; establish that they are in fact talking.
; 2. When talk detection AMI events are enabled, this value
; determines when talking has begun which results in
; an AMI event to fire. If this value is set too tight
; AMI events may be falsely triggered by variants in
; room noise.
; 3. The drop_silence option depends on this value to determine
; when the user's audio should be mixed into the bridge
; after periods of silence. If this value is too loose
; the beginning of a user's speech will get cut off as they
; transition from silence to talking.
;
; By default this value is 160 ms. Valid values are 1 through 2^31
;dsp_silence_threshold=2000 ; The time in milliseconds of sound falling within the what
; the dsp has established as baseline silence before a user
; is considered be silent. This value affects several
; operations and should not be changed unless the impact
; on call quality is fully understood.
;
; What this value affects internally:
;
; 1. When talk detection AMI events are enabled, this value
; determines when the user has stopped talking after a
; period of talking. If this value is set too low
; AMI events indicating the user has stopped talking
; may get falsely sent out when the user briefly pauses
; during mid sentence.
; 2. The drop_silence option depends on this value to
; determine when the user's audio should begin to be
; dropped from the conference bridge after the user
; stops talking. If this value is set too low the user's
; audio stream may sound choppy to the other participants.
; This is caused by the user transitioning constantly from
; silence to talking during mid sentence.
;
; The best way to approach this option is to set it slightly above
; the maximum amount of ms of silence a user may generate during
; natural speech.
;
; By default this value is 2500ms. Valid values are 1 through 2^31
;talk_detection_events=yes ; This option sets whether or not notifications of when a user
; begins and ends talking should be sent out as events over AMI.
; By default this option is off.
;denoise=yes ; Sets whether or not a denoise filter should be applied
; to the audio before mixing or not. Off by default. Requires
; codec_speex to be built and installed. Do not confuse this option
; with drop_silence. Denoise is useful if there is a lot of background
; noise for a user as it attempts to remove the noise while preserving
; the speech. This option does NOT remove silence from being mixed into
; the conference and does come at the cost of a slight performance hit.
;jitterbuffer=yes ; Enabling this option places a jitterbuffer on the user's audio stream
; before audio mixing is performed. This is highly recommended but will
; add a slight delay to the audio. This option is using the JITTERBUFFER
; dialplan function's default adaptive jitterbuffer. For a more fine tuned
; jitterbuffer, disable this option and use the JITTERBUFFER dialplan function
; on the user before entering the ConfBridge application.
;pin=1234 ; Sets if this user must enter a PIN number before entering
; the conference. The PIN will be prompted for.
;announce_join_leave=yes ; When enabled, this option will prompt the user for a
; name when entering the conference. After the name is
; recorded, it will be played as the user enters and exists
; the conference. This option is off by default.
;dtmf_passthrough=yes ; Sets whether or not DTMF should pass through the conference.
; This option is off by default.
; --- ConfBridge Bridge Profile Options ---
[default_bridge]
type=bridge
;max_members=50 ; This option limits the number of participants for a single
; conference to a specific number. By default conferences
; have no participant limit. After the limit is reached, the
; conference will be locked until someone leaves. Note however
; that an Admin user will always be alowed to join the conference
; regardless if this limit is reached or not.
;record_conference=yes ; Records the conference call starting when the first user
; enters the room, and ending when the last user exits the room.
; The default recorded filename is
; 'confbridge-<name of conference bridge>-<start time>.wav
; and the default format is 8khz slinear. This file will be
; located in the configured monitoring directory in asterisk.conf.
;record_file=</path/to/file> ; When record_conference is set to yes, the specific name of the
; record file can be set using this option. Note that since multiple
; conferences may use the same bridge profile, this may cause issues
; depending on the configuration. It is recommended to only use this
; option dynamically with the CONFBRIDGE() dialplan function. This
; allows the record name to be specified and a unique name to be chosen.
; By default, the record_file is stored in Asterisk's spool/monitor directory
; with a unique filename starting with the 'confbridge' prefix.
;internal_sample_rate=auto ; Sets the internal native sample rate the
; conference is mixed at. This is set to automatically
; adjust the sample rate to the best quality by default.
; Other values can be anything from 8000-192000. If a
; sample rate is set that Asterisk does not support, the
; closest sample rate Asterisk does support to the one requested
; will be used.
;mixing_interval=40 ; Sets the internal mixing interval in milliseconds for the bridge. This
; number reflects how tight or loose the mixing will be for the conference.
; In order to improve performance a larger mixing interval such as 40ms may
; be chosen. Using a larger mixing interval comes at the cost of introducing
; larger amounts of delay into the bridge. Valid values here are 10, 20, 40,
; or 80. By default 20ms is used.
; All sounds in the conference are customizable using the bridge profile options below.
; Simply state the option followed by the filename or full path of the filename after
; the option. Example: sound_had_joined=conf-hasjoin This will play the conf-hasjoin
; sound file found in the sounds directory when announcing someone's name is joining the
; conference.
;sound_join ; The sound played to everyone when someone enters the conference.
;sound_leave ; The sound played to everyone when someone leaves the conference.
;sound_has_joined ; The sound played before announcing someone's name has
; joined the conference. This is used for user intros.
; Example "_____ has joined the conference"
;sound_has_left ; The sound played when announcing someone's name has
; left the conference. This is used for user intros.
; Example "_____ has left the conference"
;sound_kicked ; The sound played to a user who has been kicked from the conference.
;sound_muted ; The sound played when the mute option it toggled on.
;sound_unmuted ; The sound played when the mute option it toggled off.
;sound_only_person ; The sound played when the user is the only person in the conference.
;sound_only_one ; The sound played to a user when there is only one other
; person is in the conference.
;sound_there_are ; The sound played when announcing how many users there
; are in a conference.
;sound_other_in_party; ; This file is used in conjunction with 'sound_there_are"
; when announcing how many users there are in the conference.
; The sounds are stringed together like this.
; "sound_there_are" <number of participants> "sound_other_in_party"
;sound_place_into_conference ; The sound played when someone is placed into the conference
; after waiting for a marked user.
;sound_wait_for_leader ; The sound played when a user is placed into a conference that
; can not start until a marked user enters.
;sound_leader_has_left ; The sound played when the last marked user leaves the conference.
;sound_get_pin ; The sound played when prompting for a conference pin number.
;sound_invalid_pin ; The sound played when an invalid pin is entered too many times.
;sound_locked ; The sound played to a user trying to join a locked conference.
;sound_locked_now ; The sound played to an admin after toggling the conference to locked mode.
;sound_unlocked_now; The sound played to an admin after toggling the conference to unlocked mode.
;sound_error_menu ; The sound played when an invalid menu option is entered.
; --- ConfBridge Menu Options ---
; The ConfBridge application also has the ability to
; apply custom DTMF menus to each channel using the
; application. Like the User and Bridge profiles
; a menu is passed in to ConfBridge as an argument in
; the dialplan.
;
; Below is a list of menu actions that can be assigned
; to a DTMF sequence.
;
; A single DTMF sequence can have multiple actions associated with it. This is
; accomplished by stringing the actions together and using a ',' as the delimiter.
; Example: Both listening and talking volume is reset when '5' is pressed.
; 5=reset_talking_volume, reset_listening_volume
;
; playback(<name of audio file>&<name of audio file>)
; Playback will play back an audio file to a channel
; and then immediately return to the conference.
; This file can not be interupted by DTMF.
; Mutliple files can be chained together using the
; '&' character.
; playback_and_continue(<name of playback prompt>&<name of playback prompt>)
; playback_and_continue will
; play back a prompt while continuing to
; collect the dtmf sequence. This is useful
; when using a menu prompt that describes all
; the menu options. Note however that any DTMF
; during this action will terminate the prompts
; playback. Prompt files can be chained together
; using the '&' character as a delimiter.
; toggle_mute ; Toggle turning on and off mute. Mute will make the user silent
; to everyone else, but the user will still be able to listen in.
; continue to collect the dtmf sequence.
; no_op ; This action does nothing (No Operation). Its only real purpose exists for
; being able to reserve a sequence in the config as a menu exit sequence.
; decrease_listening_volume ; Decreases the channel's listening volume.
; increase_listening_volume ; Increases the channel's listening volume.
; reset_listening_volume ; Reset channel's listening volume to default level.
; decrease_talking_volume ; Decreases the channel's talking volume.
; increase_talking_volume ; Icreases the channel's talking volume.
; reset_talking_volume ; Reset channel's talking volume to default level.
;
; dialplan_exec(context,exten,priority) ; The dialplan_exec action allows a user
; to escape from the conference and execute
; commands in the dialplan. Once the dialplan
; exits the user will be put back into the
; conference. The possibilities are endless!
; leave_conference ; This action allows a user to exit the conference and continue
; execution in the dialplan.
;
; admin_kick_last ; This action allows an Admin to kick the last participant from the
; conference. This action will only work for admins which allows
; a single menu to be used for both users and admins.
;
; admin_toggle_conference_lock ; This action allows an Admin to toggle locking and
; unlocking the conference. Non admins can not use
; this action even if it is in their menu.
[sample_user_menu]
type=menu
*=playback_and_continue(conf-usermenu)
*1=toggle_mute
1=toggle_mute
*4=decrease_listening_volume
4=decrease_listening_volume
*6=increase_listening_volume
6=increase_listening_volume
*7=decrease_talking_volume
7=decrease_talking_volume
*8=no_op
8=no_op
*9=increase_talking_volume
9=increase_talking_volume
[sample_admin_menu]
type=menu
*=playback_and_continue(conf-adminmenu)
*1=toggle_mute
1=toggle_mute
*2=admin_toggle_conference_lock ; only applied to admin users
2=admin_toggle_conference_lock ; only applied to admin users
*3=admin_kick_last ; only applied to admin users
3=admin_kick_last ; only applied to admin users
*4=decrease_listening_volume
4=decrease_listening_volume
*6=increase_listening_volume
6=increase_listening_volume
*7=decrease_talking_volume
7=decrease_talking_volume
*8=no_op
8=no_op
*9=increase_talking_volume
9=increase_talking_volume

View File

@ -63,6 +63,7 @@ extern "C" {
#endif
#include "asterisk/bridging_features.h"
#include "asterisk/dsp.h"
/*! \brief Capabilities for a bridge technology */
enum ast_bridge_capability {
@ -96,6 +97,10 @@ enum ast_bridge_channel_state {
AST_BRIDGE_CHANNEL_STATE_FEATURE,
/*! Bridged channel is sending a DTMF stream out */
AST_BRIDGE_CHANNEL_STATE_DTMF,
/*! Bridged channel began talking */
AST_BRIDGE_CHANNEL_STATE_START_TALKING,
/*! Bridged channel has stopped talking */
AST_BRIDGE_CHANNEL_STATE_STOP_TALKING,
};
/*! \brief Return values for bridge technology write function */
@ -111,6 +116,22 @@ enum ast_bridge_write_result {
struct ast_bridge_technology;
struct ast_bridge;
/*!
* \brief Structure specific to bridge technologies capable of
* performing talking optimizations.
*/
struct ast_bridge_tech_optimizations {
/*! The amount of time in ms that talking must be detected before
* the dsp determines that talking has occurred */
unsigned int talking_threshold;
/*! The amount of time in ms that silence must be detected before
* the dsp determines that talking has stopped */
unsigned int silence_threshold;
/*! Whether or not the bridging technology should drop audio
* detected as silence from the mix. */
unsigned int drop_silence:1;
};
/*!
* \brief Structure that contains information regarding a channel in a bridge
*/
@ -137,6 +158,9 @@ struct ast_bridge_channel {
unsigned int suspended:1;
/*! Features structure for features that are specific to this channel */
struct ast_bridge_features *features;
/*! Technology optimization parameters used by bridging technologies capable of
* optimizing based upon talk detection. */
struct ast_bridge_tech_optimizations tech_args;
/*! Queue of DTMF digits used for DTMF streaming */
char dtmf_stream_q[8];
/*! Linked list information */
@ -149,6 +173,13 @@ struct ast_bridge_channel {
struct ast_bridge {
/*! Number of channels participating in the bridge */
int num;
/*! The internal sample rate this bridge is mixed at when multiple channels are being mixed.
* If this value is 0, the bridge technology may auto adjust the internal mixing rate. */
unsigned int internal_sample_rate;
/*! The mixing interval indicates how quickly the bridges internal mixing should occur
* for bridge technologies that mix audio. When set to 0, the bridge tech must choose a
* default interval for itself. */
unsigned int internal_mixing_interval;
/*! Bit to indicate that the bridge thread is waiting on channels in the bridge array */
unsigned int waiting:1;
/*! Bit to indicate the bridge thread should stop */
@ -236,6 +267,7 @@ int ast_bridge_destroy(struct ast_bridge *bridge);
* \param chan Channel to join
* \param swap Channel to swap out if swapping
* \param features Bridge features structure
* \param (Optional) Bridging tech optimization parameters for this channel.
*
* \retval state that channel exited the bridge with
*
@ -256,7 +288,11 @@ int ast_bridge_destroy(struct ast_bridge *bridge);
* If channel specific features are enabled a pointer to the features structure
* can be specified in the features parameter.
*/
enum ast_bridge_channel_state ast_bridge_join(struct ast_bridge *bridge, struct ast_channel *chan, struct ast_channel *swap, struct ast_bridge_features *features);
enum ast_bridge_channel_state ast_bridge_join(struct ast_bridge *bridge,
struct ast_channel *chan,
struct ast_channel *swap,
struct ast_bridge_features *features,
struct ast_bridge_tech_optimizations *tech_args);
/*! \brief Impart (non-blocking) a channel on a bridge
*
@ -419,6 +455,27 @@ int ast_bridge_unsuspend(struct ast_bridge *bridge, struct ast_channel *chan);
*/
void ast_bridge_change_state(struct ast_bridge_channel *bridge_channel, enum ast_bridge_channel_state new_state);
/*! \brief Adjust the internal mixing sample rate of a bridge used during
* multimix mode.
*
* \param bridge_channel Channel to change the sample rate on.
* \param sample rate, the sample rate to change to. If a
* value of 0 is passed here, the bridge will be free to pick
* what ever sample rate it chooses.
*
*/
void ast_bridge_set_internal_sample_rate(struct ast_bridge *bridge, unsigned int sample_rate);
/*! \brief Adjust the internal mixing interval of a bridge used during
* multimix mode.
*
* \param bridge_channel Channel to change the sample rate on.
* \param mixing_interval, the sample rate to change to. If 0 is set
* the bridge tech is free to choose any mixing interval it uses by default.
*/
void ast_bridge_set_mixing_interval(struct ast_bridge *bridge, unsigned int mixing_interval);
#if defined(__cplusplus) || defined(c_plusplus)
}
#endif

View File

@ -63,6 +63,31 @@ struct ast_bridge_channel;
*/
typedef int (*ast_bridge_features_hook_callback)(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *hook_pvt);
/*!
* \brief Features hook pvt destructor callback
*
* \param hook_pvt Private data passed in when the hook was create to destroy
*/
typedef void (*ast_bridge_features_hook_pvt_destructor)(void *hook_pvt);
/*!
* \brief Talking indicator callback
*
* \details This callback can be registered with the bridge in order
* to receive updates on when a bridge_channel has started and stopped
* talking
*
* \param bridge The bridge that the channel is part of
* \param bridge_channel Channel executing the feature
*
* \retval 0 success
* \retval -1 failure
*/
typedef void (*ast_bridge_talking_indicate_callback)(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, void *pvt_data);
typedef void (*ast_bridge_talking_indicate_destructor)(void *pvt_data);
/*!
* \brief Maximum length of a DTMF feature string
*/
@ -76,6 +101,8 @@ struct ast_bridge_features_hook {
char dtmf[MAXIMUM_DTMF_FEATURE_STRING];
/*! Callback that is called when DTMF string is matched */
ast_bridge_features_hook_callback callback;
/*! Callback to destroy hook_pvt data right before destruction. */
ast_bridge_features_hook_pvt_destructor destructor;
/*! Unique data that was passed into us */
void *hook_pvt;
/*! Linked list information */
@ -88,12 +115,21 @@ struct ast_bridge_features_hook {
struct ast_bridge_features {
/*! Attached DTMF based feature hooks */
AST_LIST_HEAD_NOLOCK(, ast_bridge_features_hook) hooks;
/*! Callback to indicate when a bridge channel has started and stopped talking */
ast_bridge_talking_indicate_callback talker_cb;
/*! Callback to destroy any pvt data stored for the talker. */
ast_bridge_talking_indicate_destructor talker_destructor_cb;
/*! Talker callback pvt data */
void *talker_pvt_data;
/*! Feature flags that are enabled */
struct ast_flags feature_flags;
/*! Bit to indicate that this structure is useful and should be considered when looking for features */
/*! Bit to indicate that the hook list is useful and should be considered when looking for DTMF features */
unsigned int usable:1;
/*! Bit to indicate whether the channel/bridge is muted or not */
unsigned int mute:1;
/*! Bit to indicate whether DTMF should be passed into the bridge tech or not. */
unsigned int dtmf_passthrough:1;
};
/*!
@ -161,6 +197,7 @@ int ast_bridge_features_unregister(enum ast_bridge_builtin_feature feature);
* \param dtmf DTMF string to be activated upon
* \param callback Function to execute upon activation
* \param hook_pvt Unique data
* \param Optional destructor callback for hook_pvt data
*
* \retval 0 on success
* \retval -1 on failure
@ -170,7 +207,7 @@ int ast_bridge_features_unregister(enum ast_bridge_builtin_feature feature);
* \code
* struct ast_bridge_features features;
* ast_bridge_features_init(&features);
* ast_bridge_features_hook(&features, "#", pound_callback, NULL);
* ast_bridge_features_hook(&features, "#", pound_callback, NULL, NULL);
* \endcode
*
* This makes the bridging core call pound_callback if a channel that has this
@ -180,7 +217,26 @@ int ast_bridge_features_unregister(enum ast_bridge_builtin_feature feature);
* \note It is important that the callback set the bridge channel state back to
* AST_BRIDGE_CHANNEL_STATE_WAIT or the bridge thread will not service the channel.
*/
int ast_bridge_features_hook(struct ast_bridge_features *features, const char *dtmf, ast_bridge_features_hook_callback callback, void *hook_pvt);
int ast_bridge_features_hook(struct ast_bridge_features *features,
const char *dtmf,
ast_bridge_features_hook_callback callback,
void *hook_pvt,
ast_bridge_features_hook_pvt_destructor destructor);
/*! \brief Set a callback on the features structure to receive talking notifications on.
*
* \param features Bridge features structure
* \param talker_cb, Callback function to execute when talking events occur in the bridge core.
* \param pvt_data Optional unique data that will be passed with the talking events.
* \param Optional destructor callback for pvt data.
*
* \retval 0, success
* \retval -1, failure
*/
int ast_bridge_features_set_talk_detector(struct ast_bridge_features *features,
ast_bridge_talking_indicate_callback talker_cb,
ast_bridge_talking_indicate_destructor talker_destructor,
void *pvt_data);
/*! \brief Enable a built in feature on a bridge features structure
*

View File

@ -143,6 +143,21 @@ int ast_bridge_technology_unregister(struct ast_bridge_technology *technology);
*/
void ast_bridge_handle_trip(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_channel *chan, int outfd);
/*! \brief Lets the bridging indicate when a bridge channel has stopped or started talking.
*
* \note All DSP functionality on the bridge has been pushed down to the lowest possible
* layer, which in this case is the specific bridging technology being used. Since it
* is necessary for the knowledge of which channels are talking to make its way up to the
* application, this function has been created to allow the bridging technology to communicate
* that information with the bridging core.
*
* \param bridge The bridge that the channel is a part of.
* \param bridge_channel The bridge channel that has either started or stopped talking.
* \param started_talking, set to 1 when this indicates the channel has started talking, set to 0
* when this indicates the channel has stopped talking.
*/
void ast_bridge_notify_talking(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, int started_talking);
/*! \brief Suspend a bridge technology from consideration
*
* \param technology The bridge technology to suspend

View File

@ -178,6 +178,7 @@ typedef unsigned long long ast_group_t;
*/
struct ast_generator {
void *(*alloc)(struct ast_channel *chan, void *params);
/*! Channel is locked during this function callback. */
void (*release)(struct ast_channel *chan, void *data);
/*! This function gets called with the channel unlocked, but is called in
* the context of the channel thread so we know the channel is not going
@ -186,6 +187,9 @@ struct ast_generator {
int (*generate)(struct ast_channel *chan, void *data, int len, int samples);
/*! This gets called when DTMF_END frames are read from the channel */
void (*digit)(struct ast_channel *chan, char digit);
/*! This gets called when the write format on a channel is changed while
* generating. The channel is locked during this callback. */
void (*write_format_change)(struct ast_channel *chan, void *data);
};
/*! Party name character set enumeration values (values from Q.SIG) */

View File

@ -73,9 +73,19 @@ enum threshold {
THRESHOLD_MAX = 1,
};
/*! \brief Allocates a new dsp with a specific internal sample rate used
* during processing. */
struct ast_dsp *ast_dsp_new_with_rate(unsigned int sample_rate);
/*! \brief Allocates a new dsp, assumes 8khz for internal sample rate */
struct ast_dsp *ast_dsp_new(void);
void ast_dsp_free(struct ast_dsp *dsp);
/*! \brief Retrieve the sample rate this DSP structure was
* created with */
unsigned int ast_dsp_get_sample_rate(const struct ast_dsp *dsp);
/*! \brief Set threshold value for silence */
void ast_dsp_set_threshold(struct ast_dsp *dsp, int threshold);

View File

@ -123,9 +123,9 @@ void ast_bridge_change_state(struct ast_bridge_channel *bridge_channel, enum ast
/* Only poke the channel's thread if it is not us */
if (!pthread_equal(pthread_self(), bridge_channel->thread)) {
pthread_kill(bridge_channel->thread, SIGURG);
ast_mutex_lock(&bridge_channel->lock);
ao2_lock(bridge_channel);
ast_cond_signal(&bridge_channel->cond);
ast_mutex_unlock(&bridge_channel->lock);
ao2_unlock(bridge_channel);
}
return;
@ -273,6 +273,15 @@ static int bridge_drop_control_frame(int subclass)
}
}
void ast_bridge_notify_talking(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, int started_talking)
{
if (started_talking) {
ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_START_TALKING);
} else {
ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_STOP_TALKING);
}
}
void ast_bridge_handle_trip(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_channel *chan, int outfd)
{
/* If no bridge channel has been provided and the actual channel has been provided find it */
@ -290,14 +299,21 @@ void ast_bridge_handle_trip(struct ast_bridge *bridge, struct ast_bridge_channel
ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_END);
} else if (frame->frametype == AST_FRAME_CONTROL && bridge_drop_control_frame(frame->subclass.integer)) {
ast_debug(1, "Dropping control frame from bridge channel %p\n", bridge_channel);
} else {
} else if (frame->frametype == AST_FRAME_DTMF_BEGIN || frame->frametype == AST_FRAME_DTMF_END) {
int dtmf_passthrough = bridge_channel->features ?
bridge_channel->features->dtmf_passthrough :
bridge->features.dtmf_passthrough;
if (frame->frametype == AST_FRAME_DTMF_BEGIN) {
frame = bridge_handle_dtmf(bridge, bridge_channel, frame);
}
/* Simply write the frame out to the bridge technology if it still exists */
if (frame) {
if (frame && dtmf_passthrough) {
bridge->technology->write(bridge, bridge_channel, frame);
}
} else {
/* Simply write the frame out to the bridge technology if it still exists */
bridge->technology->write(bridge, bridge_channel, frame);
}
if (frame) {
@ -681,9 +697,9 @@ static int smart_bridge_operation(struct ast_bridge *bridge, struct ast_bridge_c
/* Fourth we tell them to wake up so they become aware that they above has happened */
pthread_kill(bridge_channel2->thread, SIGURG);
ast_mutex_lock(&bridge_channel2->lock);
ao2_lock(bridge_channel2);
ast_cond_signal(&bridge_channel2->cond);
ast_mutex_unlock(&bridge_channel2->lock);
ao2_unlock(bridge_channel2);
}
/* Now that all the channels have been moved over we need to get rid of all the information the old technology may have left around */
@ -724,10 +740,10 @@ static enum ast_bridge_channel_state bridge_channel_join_multithreaded(struct as
ast_debug(10, "Going into a multithreaded waitfor for bridge channel %p of bridge %p\n", bridge_channel, bridge_channel->bridge);
chan = ast_waitfor_nandfds(&bridge_channel->chan, 1, fds, nfds, NULL, &outfd, &ms);
} else {
ast_mutex_lock(&bridge_channel->lock);
ao2_lock(bridge_channel);
ast_debug(10, "Going into a multithreaded signal wait for bridge channel %p of bridge %p\n", bridge_channel, bridge_channel->bridge);
ast_cond_wait(&bridge_channel->cond, &bridge_channel->lock);
ast_mutex_unlock(&bridge_channel->lock);
ast_cond_wait(&bridge_channel->cond, ao2_object_get_lockaddr(bridge_channel));
ao2_unlock(bridge_channel);
}
ao2_lock(bridge_channel->bridge);
@ -743,12 +759,12 @@ static enum ast_bridge_channel_state bridge_channel_join_multithreaded(struct as
static enum ast_bridge_channel_state bridge_channel_join_singlethreaded(struct ast_bridge_channel *bridge_channel)
{
ao2_unlock(bridge_channel->bridge);
ast_mutex_lock(&bridge_channel->lock);
ao2_lock(bridge_channel);
if (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_WAIT) {
ast_debug(1, "Going into a single threaded signal wait for bridge channel %p of bridge %p\n", bridge_channel, bridge_channel->bridge);
ast_cond_wait(&bridge_channel->cond, &bridge_channel->lock);
ast_cond_wait(&bridge_channel->cond, ao2_object_get_lockaddr(bridge_channel));
}
ast_mutex_unlock(&bridge_channel->lock);
ao2_unlock(bridge_channel);
ao2_lock(bridge_channel->bridge);
return bridge_channel->state;
@ -782,7 +798,11 @@ static void bridge_channel_unsuspend(struct ast_bridge *bridge, struct ast_bridg
return;
}
/*! \brief Internal function that executes a feature on a bridge channel */
/*!
* \brief Internal function that executes a feature on a bridge channel
* \note Neither the bridge nor the bridge_channel locks should be held when entering
* this function.
*/
static void bridge_channel_feature(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
{
struct ast_bridge_features *features = (bridge_channel->features ? bridge_channel->features : &bridge->features);
@ -819,6 +839,7 @@ static void bridge_channel_feature(struct ast_bridge *bridge, struct ast_bridge_
/* If this hook matches just break out now */
if (!strcmp(hook->dtmf, dtmf)) {
ast_debug(1, "DTMF feature hook %p matched DTMF string '%s' on bridge channel %p\n", hook, dtmf, bridge_channel);
look_for_dtmf = 0;
break;
} else if (!strncmp(hook->dtmf, dtmf, dtmf_len)) {
ast_debug(1, "DTMF feature hook %p can match DTMF string '%s', it wants '%s', on bridge channel %p\n", hook, dtmf, hook->dtmf, bridge_channel);
@ -842,12 +863,26 @@ static void bridge_channel_feature(struct ast_bridge *bridge, struct ast_bridge_
hook->callback(bridge, bridge_channel, hook->hook_pvt);
} else {
ast_bridge_dtmf_stream(bridge, dtmf, bridge_channel->chan);
}
/* if the channel is still in feature state, revert it back to wait state */
if (bridge_channel->state == AST_BRIDGE_CHANNEL_STATE_FEATURE) {
ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_WAIT);
}
return;
}
static void bridge_channel_talking(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
{
struct ast_bridge_features *features = (bridge_channel->features ? bridge_channel->features : &bridge->features);
if (features && features->talker_cb) {
features->talker_cb(bridge, bridge_channel, features->talker_pvt_data);
}
ast_bridge_change_state(bridge_channel, AST_BRIDGE_CHANNEL_STATE_WAIT);
}
/*! \brief Internal function that plays back DTMF on a bridge channel */
static void bridge_channel_dtmf_stream(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
{
@ -890,7 +925,10 @@ static enum ast_bridge_channel_state bridge_channel_join(struct ast_bridge_chann
if (bridge_channel->swap) {
struct ast_bridge_channel *bridge_channel2 = NULL;
/* If we are performing a swap operation we do not need to execute the smart bridge operation as the actual number of channels involved will not have changed, we just need to tell the other channel to leave */
/* If we are performing a swap operation we do not need
* to execute the smart bridge operation as the actual number
* of channels involved will not have changed, we just need to
* tell the other channel to leave */
if ((bridge_channel2 = find_bridge_channel(bridge_channel->bridge, bridge_channel->swap))) {
ast_debug(1, "Swapping bridge channel %p out from bridge %p so bridge channel %p can slip in\n", bridge_channel2, bridge_channel->bridge, bridge_channel);
ast_bridge_change_state(bridge_channel2, AST_BRIDGE_CHANNEL_STATE_HANGUP);
@ -931,14 +969,27 @@ static enum ast_bridge_channel_state bridge_channel_join(struct ast_bridge_chann
/* Execute the threading model */
state = (bridge_channel->bridge->technology->capabilities & AST_BRIDGE_CAPABILITY_MULTITHREADED ? bridge_channel_join_multithreaded(bridge_channel) : bridge_channel_join_singlethreaded(bridge_channel));
/* Depending on the above state see what we need to do */
if (state == AST_BRIDGE_CHANNEL_STATE_FEATURE) {
switch (state) {
case AST_BRIDGE_CHANNEL_STATE_FEATURE:
bridge_channel_suspend(bridge_channel->bridge, bridge_channel);
ao2_unlock(bridge_channel->bridge);
bridge_channel_feature(bridge_channel->bridge, bridge_channel);
ao2_lock(bridge_channel->bridge);
bridge_channel_unsuspend(bridge_channel->bridge, bridge_channel);
} else if (state == AST_BRIDGE_CHANNEL_STATE_DTMF) {
break;
case AST_BRIDGE_CHANNEL_STATE_DTMF:
bridge_channel_suspend(bridge_channel->bridge, bridge_channel);
bridge_channel_dtmf_stream(bridge_channel->bridge, bridge_channel);
bridge_channel_unsuspend(bridge_channel->bridge, bridge_channel);
break;
case AST_BRIDGE_CHANNEL_STATE_START_TALKING:
case AST_BRIDGE_CHANNEL_STATE_STOP_TALKING:
ao2_unlock(bridge_channel->bridge);
bridge_channel_talking(bridge_channel->bridge, bridge_channel);
ao2_lock(bridge_channel->bridge);
break;
default:
break;
}
}
@ -987,29 +1038,63 @@ static enum ast_bridge_channel_state bridge_channel_join(struct ast_bridge_chann
return bridge_channel->state;
}
enum ast_bridge_channel_state ast_bridge_join(struct ast_bridge *bridge, struct ast_channel *chan, struct ast_channel *swap, struct ast_bridge_features *features)
static void bridge_channel_destroy(void *obj)
{
struct ast_bridge_channel bridge_channel = {
.chan = chan,
.swap = swap,
.bridge = bridge,
.features = features,
};
enum ast_bridge_channel_state state;
struct ast_bridge_channel *bridge_channel = obj;
if (bridge_channel->bridge) {
ao2_ref(bridge_channel->bridge, -1);
bridge_channel->bridge = NULL;
}
/* Destroy elements of the bridge channel structure and the bridge channel structure itself */
ast_cond_destroy(&bridge_channel->cond);
}
static struct ast_bridge_channel *bridge_channel_alloc(struct ast_bridge *bridge)
{
struct ast_bridge_channel *bridge_channel = ao2_alloc(sizeof(struct ast_bridge_channel), bridge_channel_destroy);
if (!(bridge_channel)) {
return NULL;
}
ast_cond_init(&bridge_channel->cond, NULL);
if (bridge) {
bridge_channel->bridge = bridge;
ao2_ref(bridge_channel->bridge, +1);
}
return bridge_channel;
}
enum ast_bridge_channel_state ast_bridge_join(struct ast_bridge *bridge,
struct ast_channel *chan,
struct ast_channel *swap,
struct ast_bridge_features *features,
struct ast_bridge_tech_optimizations *tech_args)
{
struct ast_bridge_channel *bridge_channel = bridge_channel_alloc(bridge);
enum ast_bridge_channel_state state = AST_BRIDGE_CHANNEL_STATE_HANGUP;
if (!bridge_channel) {
return state;
}
if (tech_args) {
memcpy(&bridge_channel->tech_args, tech_args, sizeof(bridge_channel->tech_args));
}
/* Initialize various other elements of the bridge channel structure that we can't do above */
ast_mutex_init(&bridge_channel.lock);
ast_cond_init(&bridge_channel.cond, NULL);
bridge_channel->chan = chan;
bridge_channel->swap = swap;
bridge_channel->features = features;
ao2_ref(bridge_channel.bridge, +1);
state = bridge_channel_join(bridge_channel);
state = bridge_channel_join(&bridge_channel);
/* Cleanup all the data in the bridge channel after it leaves the bridge. */
ao2_lock(bridge_channel);
bridge_channel->chan = NULL;
bridge_channel->swap = NULL;
bridge_channel->features = NULL;
ao2_unlock(bridge_channel);
ao2_ref(bridge_channel.bridge, -1);
/* Destroy some elements of the bridge channel structure above */
ast_mutex_destroy(&bridge_channel.lock);
ast_cond_destroy(&bridge_channel.cond);
ao2_ref(bridge_channel, -1);
return state;
}
@ -1022,49 +1107,39 @@ static void *bridge_channel_thread(void *data)
state = bridge_channel_join(bridge_channel);
ao2_ref(bridge_channel->bridge, -1);
/* If no other thread is going to take the channel then hang it up, or else we would have to service it until something else came along */
if (state == AST_BRIDGE_CHANNEL_STATE_END || state == AST_BRIDGE_CHANNEL_STATE_HANGUP) {
ast_hangup(bridge_channel->chan);
}
/* Destroy elements of the bridge channel structure and the bridge channel structure itself */
ast_mutex_destroy(&bridge_channel->lock);
ast_cond_destroy(&bridge_channel->cond);
ast_free(bridge_channel);
/* cleanup */
ao2_lock(bridge_channel);
bridge_channel->chan = NULL;
bridge_channel->swap = NULL;
bridge_channel->features = NULL;
ao2_unlock(bridge_channel);
ao2_ref(bridge_channel, -1);
return NULL;
}
int ast_bridge_impart(struct ast_bridge *bridge, struct ast_channel *chan, struct ast_channel *swap, struct ast_bridge_features *features)
{
struct ast_bridge_channel *bridge_channel = NULL;
struct ast_bridge_channel *bridge_channel = bridge_channel_alloc(bridge);
/* Try to allocate a structure for the bridge channel */
if (!(bridge_channel = ast_calloc(1, sizeof(*bridge_channel)))) {
if (!(bridge_channel)) {
return -1;
}
/* Setup various parameters */
bridge_channel->chan = chan;
bridge_channel->swap = swap;
bridge_channel->bridge = bridge;
bridge_channel->features = features;
/* Initialize our mutex lock and condition */
ast_mutex_init(&bridge_channel->lock);
ast_cond_init(&bridge_channel->cond, NULL);
/* Bump up the reference count on the bridge, it'll get decremented later */
ao2_ref(bridge, +1);
/* Actually create the thread that will handle the channel */
if (ast_pthread_create(&bridge_channel->thread, NULL, bridge_channel_thread, bridge_channel)) {
ao2_ref(bridge, -1);
ast_cond_destroy(&bridge_channel->cond);
ast_mutex_destroy(&bridge_channel->lock);
ast_free(bridge_channel);
ao2_ref(bridge_channel, -1);
return -1;
}
@ -1181,9 +1256,9 @@ int ast_bridge_merge(struct ast_bridge *bridge0, struct ast_bridge *bridge1)
/* Poke the bridge channel, this will cause it to wake up and execute the proper threading model for the new bridge it is in */
pthread_kill(bridge_channel->thread, SIGURG);
ast_mutex_lock(&bridge_channel->lock);
ao2_lock(bridge_channel);
ast_cond_signal(&bridge_channel->cond);
ast_mutex_unlock(&bridge_channel->lock);
ao2_unlock(bridge_channel);
}
ast_debug(1, "Merged channels from bridge %p into bridge %p\n", bridge1, bridge0);
@ -1268,7 +1343,11 @@ int ast_bridge_features_unregister(enum ast_bridge_builtin_feature feature)
return 0;
}
int ast_bridge_features_hook(struct ast_bridge_features *features, const char *dtmf, ast_bridge_features_hook_callback callback, void *hook_pvt)
int ast_bridge_features_hook(struct ast_bridge_features *features,
const char *dtmf,
ast_bridge_features_hook_callback callback,
void *hook_pvt,
ast_bridge_features_hook_pvt_destructor destructor)
{
struct ast_bridge_features_hook *hook = NULL;
@ -1279,6 +1358,7 @@ int ast_bridge_features_hook(struct ast_bridge_features *features, const char *d
ast_copy_string(hook->dtmf, dtmf, sizeof(hook->dtmf));
hook->callback = callback;
hook->destructor = destructor;
hook->hook_pvt = hook_pvt;
/* Once done we add it onto the list. Now it will be picked up when DTMF is used */
@ -1289,6 +1369,17 @@ int ast_bridge_features_hook(struct ast_bridge_features *features, const char *d
return 0;
}
int ast_bridge_features_set_talk_detector(struct ast_bridge_features *features,
ast_bridge_talking_indicate_callback talker_cb,
ast_bridge_talking_indicate_destructor talker_destructor,
void *pvt_data)
{
features->talker_cb = talker_cb;
features->talker_destructor_cb = talker_destructor;
features->talker_pvt_data = pvt_data;
return 0;
}
int ast_bridge_features_enable(struct ast_bridge_features *features, enum ast_bridge_builtin_feature feature, const char *dtmf, void *config)
{
/* If no alternate DTMF stream was provided use the default one */
@ -1306,7 +1397,7 @@ int ast_bridge_features_enable(struct ast_bridge_features *features, enum ast_br
}
/* The rest is basically pretty easy. We create another hook using the built in feature's callback and DTMF, easy as pie. */
return ast_bridge_features_hook(features, dtmf, builtin_features_handlers[feature], config);
return ast_bridge_features_hook(features, dtmf, builtin_features_handlers[feature], config, NULL);
}
int ast_bridge_features_set_flag(struct ast_bridge_features *features, enum ast_bridge_feature_flags flag)
@ -1333,8 +1424,15 @@ int ast_bridge_features_cleanup(struct ast_bridge_features *features)
/* This is relatively simple, hooks are kept as a list on the features structure so we just pop them off and free them */
while ((hook = AST_LIST_REMOVE_HEAD(&features->hooks, entry))) {
if (hook->destructor) {
hook->destructor(hook->hook_pvt);
}
ast_free(hook);
}
if (features->talker_destructor_cb && features->talker_pvt_data) {
features->talker_destructor_cb(features->talker_pvt_data);
features->talker_pvt_data = NULL;
}
return 0;
}
@ -1357,3 +1455,18 @@ int ast_bridge_dtmf_stream(struct ast_bridge *bridge, const char *dtmf, struct a
return 0;
}
void ast_bridge_set_mixing_interval(struct ast_bridge *bridge, unsigned int mixing_interval)
{
ao2_lock(bridge);
bridge->internal_mixing_interval = mixing_interval;
ao2_unlock(bridge);
}
void ast_bridge_set_internal_sample_rate(struct ast_bridge *bridge, unsigned int sample_rate)
{
ao2_lock(bridge);
bridge->internal_sample_rate = sample_rate;
ao2_unlock(bridge);
}

View File

@ -3011,6 +3011,15 @@ void ast_deactivate_generator(struct ast_channel *chan)
ast_channel_unlock(chan);
}
static void generator_write_format_change(struct ast_channel *chan)
{
ast_channel_lock(chan);
if (chan->generator && chan->generator->write_format_change) {
chan->generator->write_format_change(chan, chan->generatordata);
}
ast_channel_unlock(chan);
}
static int generator_force(const void *data)
{
/* Called if generator doesn't have data */
@ -5035,10 +5044,9 @@ static int set_format(struct ast_channel *chan,
ast_debug(1, "Channel driver natively set channel %s to %s format %s\n", chan->name,
direction ? "write" : "read", ast_getformatname(&best_set_fmt));
ast_channel_lock(chan);
ast_format_copy(format, &best_set_fmt);
ast_format_copy(rawformat, &best_set_fmt);
ast_channel_lock(chan);
ast_format_cap_set(chan->nativeformats, &best_set_fmt);
ast_channel_unlock(chan);
@ -5046,6 +5054,11 @@ static int set_format(struct ast_channel *chan,
ast_translator_free_path(*trans);
}
*trans = NULL;
/* If there is a generator on the channel, it needs to know about this
* change if it is the write format. */
if (direction && chan->generatordata) {
generator_write_format_change(chan);
}
return 0;
}
@ -5110,6 +5123,12 @@ static int set_format(struct ast_channel *chan,
chan->name,
direction ? "write" : "read",
ast_getformatname(&best_set_fmt));
/* If there is a generator on the channel, it needs to know about this
* change if it is the write format. */
if (direction && chan->generatordata) {
generator_write_format_change(chan);
}
return res;
}

View File

@ -192,15 +192,7 @@ enum gsamp_thresh {
#define FAX_TONE_CED_DURATION 2600
#define FAX_TONE_CED_DB 16
#define SAMPLE_RATE 8000
/* How many samples a frame has. This constant is used when calculating
* Goertzel block size for tone_detect. It is only important if we want to
* remove (squelch) the tone. In this case it is important to have block
* size not to exceed size of voice frame. Otherwise by the moment the tone
* is detected it is too late to squelch it from previous frames.
*/
#define SAMPLES_IN_FRAME 160
#define DEFAULT_SAMPLE_RATE 8000
/* MF goertzel size */
#define MF_GSIZE 120
@ -339,10 +331,10 @@ static inline float goertzel_result(goertzel_state_t *s)
return (float)r.value * (float)(1 << r.power);
}
static inline void goertzel_init(goertzel_state_t *s, float freq, int samples)
static inline void goertzel_init(goertzel_state_t *s, float freq, int samples, unsigned int sample_rate)
{
s->v2 = s->v3 = s->chunky = 0.0;
s->fac = (int)(32768.0 * 2.0 * cos(2.0 * M_PI * freq / SAMPLE_RATE));
s->fac = (int)(32768.0 * 2.0 * cos(2.0 * M_PI * freq / sample_rate));
s->samples = samples;
}
@ -394,6 +386,7 @@ struct ast_dsp {
int display_inband_dtmf_warning;
float genergy;
int mute_fragments;
unsigned int sample_rate;
fragment_t mute_data[5];
digit_detect_state_t digit_state;
tone_detect_state_t cng_tone_state;
@ -410,7 +403,7 @@ static void mute_fragment(struct ast_dsp *dsp, fragment_t *fragment)
dsp->mute_data[dsp->mute_fragments++] = *fragment;
}
static void ast_tone_detect_init(tone_detect_state_t *s, int freq, int duration, int amp)
static void ast_tone_detect_init(tone_detect_state_t *s, int freq, int duration, int amp, unsigned int sample_rate)
{
int duration_samples;
float x;
@ -419,16 +412,16 @@ static void ast_tone_detect_init(tone_detect_state_t *s, int freq, int duration,
s->freq = freq;
/* Desired tone duration in samples */
duration_samples = duration * SAMPLE_RATE / 1000;
duration_samples = duration * sample_rate / 1000;
/* We want to allow 10% deviation of tone duration */
duration_samples = duration_samples * 9 / 10;
/* If we want to remove tone, it is important to have block size not
to exceed frame size. Otherwise by the moment tone is detected it is too late
to squelch it from previous frames */
s->block_size = SAMPLES_IN_FRAME;
to squelch it from previous frames. Block size is 20ms at the given sample rate.*/
s->block_size = (20 * sample_rate) / 1000;
periods_in_block = s->block_size * freq / SAMPLE_RATE;
periods_in_block = s->block_size * freq / sample_rate;
/* Make sure we will have at least 5 periods at target frequency for analisys.
This may make block larger than expected packet and will make squelching impossible
@ -437,7 +430,7 @@ static void ast_tone_detect_init(tone_detect_state_t *s, int freq, int duration,
periods_in_block = 5;
/* Now calculate final block size. It will contain integer number of periods */
s->block_size = periods_in_block * SAMPLE_RATE / freq;
s->block_size = periods_in_block * sample_rate / freq;
/* tone_detect is currently only used to detect fax tones and we
do not need suqlching the fax tones */
@ -447,7 +440,7 @@ static void ast_tone_detect_init(tone_detect_state_t *s, int freq, int duration,
and thus no tone will be detected in them */
s->hits_required = (duration_samples - (s->block_size - 1)) / s->block_size;
goertzel_init(&s->tone, freq, s->block_size);
goertzel_init(&s->tone, freq, s->block_size, sample_rate);
s->samples_pending = s->block_size;
s->hit_count = 0;
@ -472,19 +465,19 @@ static void ast_tone_detect_init(tone_detect_state_t *s, int freq, int duration,
static void ast_fax_detect_init(struct ast_dsp *s)
{
ast_tone_detect_init(&s->cng_tone_state, FAX_TONE_CNG_FREQ, FAX_TONE_CNG_DURATION, FAX_TONE_CNG_DB);
ast_tone_detect_init(&s->ced_tone_state, FAX_TONE_CED_FREQ, FAX_TONE_CED_DURATION, FAX_TONE_CED_DB);
ast_tone_detect_init(&s->cng_tone_state, FAX_TONE_CNG_FREQ, FAX_TONE_CNG_DURATION, FAX_TONE_CNG_DB, s->sample_rate);
ast_tone_detect_init(&s->ced_tone_state, FAX_TONE_CED_FREQ, FAX_TONE_CED_DURATION, FAX_TONE_CED_DB, s->sample_rate);
}
static void ast_dtmf_detect_init (dtmf_detect_state_t *s)
static void ast_dtmf_detect_init (dtmf_detect_state_t *s, unsigned int sample_rate)
{
int i;
s->lasthit = 0;
s->current_hit = 0;
for (i = 0; i < 4; i++) {
goertzel_init(&s->row_out[i], dtmf_row[i], DTMF_GSIZE);
goertzel_init(&s->col_out[i], dtmf_col[i], DTMF_GSIZE);
goertzel_init(&s->row_out[i], dtmf_row[i], DTMF_GSIZE, sample_rate);
goertzel_init(&s->col_out[i], dtmf_col[i], DTMF_GSIZE, sample_rate);
s->energy = 0.0;
}
s->current_sample = 0;
@ -495,18 +488,18 @@ static void ast_dtmf_detect_init (dtmf_detect_state_t *s)
s->misses_to_end = DTMF_MISSES_TO_END;
}
static void ast_mf_detect_init (mf_detect_state_t *s)
static void ast_mf_detect_init (mf_detect_state_t *s, unsigned int sample_rate)
{
int i;
s->hits[0] = s->hits[1] = s->hits[2] = s->hits[3] = s->hits[4] = 0;
for (i = 0; i < 6; i++) {
goertzel_init (&s->tone_out[i], mf_tones[i], 160);
goertzel_init (&s->tone_out[i], mf_tones[i], 160, sample_rate);
}
s->current_sample = 0;
s->current_hit = 0;
}
static void ast_digit_detect_init(digit_detect_state_t *s, int mf)
static void ast_digit_detect_init(digit_detect_state_t *s, int mf, unsigned int sample_rate)
{
s->current_digits = 0;
s->detected_digits = 0;
@ -514,9 +507,9 @@ static void ast_digit_detect_init(digit_detect_state_t *s, int mf)
s->digits[0] = '\0';
if (mf) {
ast_mf_detect_init(&s->td.mf);
ast_mf_detect_init(&s->td.mf, sample_rate);
} else {
ast_dtmf_detect_init(&s->td.dtmf);
ast_dtmf_detect_init(&s->td.dtmf, sample_rate);
}
}
@ -1105,7 +1098,7 @@ int ast_dsp_call_progress(struct ast_dsp *dsp, struct ast_frame *inf)
ast_log(LOG_WARNING, "Can't check call progress of non-voice frames\n");
return 0;
}
if (inf->subclass.format.id != AST_FORMAT_SLINEAR) {
if (!ast_format_is_slinear(&inf->subclass.format)) {
ast_log(LOG_WARNING, "Can only check call progress in signed-linear frames\n");
return 0;
}
@ -1128,7 +1121,7 @@ static int __ast_dsp_silence_noise(struct ast_dsp *dsp, short *s, int len, int *
accum /= len;
if (accum < dsp->threshold) {
/* Silent */
dsp->totalsilence += len / 8;
dsp->totalsilence += len / (dsp->sample_rate / 1000);
if (dsp->totalnoise) {
/* Move and save history */
memmove(dsp->historicnoise + DSP_HISTORY - dsp->busycount, dsp->historicnoise + DSP_HISTORY - dsp->busycount + 1, dsp->busycount * sizeof(dsp->historicnoise[0]));
@ -1142,7 +1135,7 @@ static int __ast_dsp_silence_noise(struct ast_dsp *dsp, short *s, int len, int *
res = 1;
} else {
/* Not silent */
dsp->totalnoise += len / 8;
dsp->totalnoise += len / (dsp->sample_rate / 1000);
if (dsp->totalsilence) {
int silence1 = dsp->historicsilence[DSP_HISTORY - 1];
int silence2 = dsp->historicsilence[DSP_HISTORY - 2];
@ -1321,7 +1314,7 @@ int ast_dsp_silence(struct ast_dsp *dsp, struct ast_frame *f, int *totalsilence)
ast_log(LOG_WARNING, "Can't calculate silence on a non-voice frame\n");
return 0;
}
if (f->subclass.format.id != AST_FORMAT_SLINEAR) {
if (!ast_format_is_slinear(&f->subclass.format)) {
ast_log(LOG_WARNING, "Can only calculate silence on signed-linear frames :(\n");
return 0;
}
@ -1339,7 +1332,7 @@ int ast_dsp_noise(struct ast_dsp *dsp, struct ast_frame *f, int *totalnoise)
ast_log(LOG_WARNING, "Can't calculate noise on a non-voice frame\n");
return 0;
}
if (f->subclass.format.id != AST_FORMAT_SLINEAR) {
if (!ast_format_is_slinear(&f->subclass.format)) {
ast_log(LOG_WARNING, "Can only calculate noise on signed-linear frames :(\n");
return 0;
}
@ -1370,30 +1363,31 @@ struct ast_frame *ast_dsp_process(struct ast_channel *chan, struct ast_dsp *dsp,
odata = af->data.ptr;
len = af->datalen;
/* Make sure we have short data */
switch (af->subclass.format.id) {
case AST_FORMAT_SLINEAR:
if (ast_format_is_slinear(&af->subclass.format)) {
shortdata = af->data.ptr;
len = af->datalen / 2;
break;
case AST_FORMAT_ULAW:
case AST_FORMAT_TESTLAW:
shortdata = alloca(af->datalen * 2);
for (x = 0;x < len; x++) {
shortdata[x] = AST_MULAW(odata[x]);
} else {
switch (af->subclass.format.id) {
case AST_FORMAT_ULAW:
case AST_FORMAT_TESTLAW:
shortdata = alloca(af->datalen * 2);
for (x = 0;x < len; x++) {
shortdata[x] = AST_MULAW(odata[x]);
}
break;
case AST_FORMAT_ALAW:
shortdata = alloca(af->datalen * 2);
for (x = 0; x < len; x++) {
shortdata[x] = AST_ALAW(odata[x]);
}
break;
default:
/*Display warning only once. Otherwise you would get hundreds of warnings every second */
if (dsp->display_inband_dtmf_warning)
ast_log(LOG_WARNING, "Inband DTMF is not supported on codec %s. Use RFC2833\n", ast_getformatname(&af->subclass.format));
dsp->display_inband_dtmf_warning = 0;
return af;
}
break;
case AST_FORMAT_ALAW:
shortdata = alloca(af->datalen * 2);
for (x = 0; x < len; x++) {
shortdata[x] = AST_ALAW(odata[x]);
}
break;
default:
/*Display warning only once. Otherwise you would get hundreds of warnings every second */
if (dsp->display_inband_dtmf_warning)
ast_log(LOG_WARNING, "Inband DTMF is not supported on codec %s. Use RFC2833\n", ast_getformatname(&af->subclass.format));
dsp->display_inband_dtmf_warning = 0;
return af;
}
/* Initially we do not want to mute anything */
@ -1454,7 +1448,7 @@ struct ast_frame *ast_dsp_process(struct ast_channel *chan, struct ast_dsp *dsp,
if (dsp->features & DSP_FEATURE_DIGIT_DETECT) {
event = AST_FRAME_DTMF_END;
event_digit = dsp->digit_state.digits[0];
event_len = dsp->digit_state.digitlen[0] * 1000 / SAMPLE_RATE;
event_len = dsp->digit_state.digitlen[0] * 1000 / dsp->sample_rate;
}
memmove(&dsp->digit_state.digits[0], &dsp->digit_state.digits[1], dsp->digit_state.current_digits);
memmove(&dsp->digit_state.digitlen[0], &dsp->digit_state.digitlen[1], dsp->digit_state.current_digits * sizeof(dsp->digit_state.digitlen[0]));
@ -1521,8 +1515,6 @@ done:
}
switch (af->subclass.format.id) {
case AST_FORMAT_SLINEAR:
break;
case AST_FORMAT_ULAW:
for (x = 0; x < len; x++) {
odata[x] = AST_LIN2MU((unsigned short) shortdata[x]);
@ -1557,7 +1549,7 @@ static void ast_dsp_prog_reset(struct ast_dsp *dsp)
dsp->gsamps = 0;
for (x = 0; x < ARRAY_LEN(modes[dsp->progmode].freqs); x++) {
if (modes[dsp->progmode].freqs[x]) {
goertzel_init(&dsp->freqs[x], (float)modes[dsp->progmode].freqs[x], dsp->gsamp_size);
goertzel_init(&dsp->freqs[x], (float)modes[dsp->progmode].freqs[x], dsp->gsamp_size, dsp->sample_rate);
max = x + 1;
}
}
@ -1565,7 +1557,12 @@ static void ast_dsp_prog_reset(struct ast_dsp *dsp)
dsp->ringtimeout= 0;
}
struct ast_dsp *ast_dsp_new(void)
unsigned int ast_dsp_get_sample_rate(const struct ast_dsp *dsp)
{
return dsp->sample_rate;
}
static struct ast_dsp *__ast_dsp_new(unsigned int sample_rate)
{
struct ast_dsp *dsp;
@ -1575,8 +1572,9 @@ struct ast_dsp *ast_dsp_new(void)
dsp->busycount = DSP_HISTORY;
dsp->digitmode = DSP_DIGITMODE_DTMF;
dsp->faxmode = DSP_FAXMODE_DETECT_CNG;
dsp->sample_rate = sample_rate;
/* Initialize digit detector */
ast_digit_detect_init(&dsp->digit_state, dsp->digitmode & DSP_DIGITMODE_MF);
ast_digit_detect_init(&dsp->digit_state, dsp->digitmode & DSP_DIGITMODE_MF, dsp->sample_rate);
dsp->display_inband_dtmf_warning = 1;
/* Initialize initial DSP progress detect parameters */
ast_dsp_prog_reset(dsp);
@ -1586,6 +1584,16 @@ struct ast_dsp *ast_dsp_new(void)
return dsp;
}
struct ast_dsp *ast_dsp_new(void)
{
return __ast_dsp_new(DEFAULT_SAMPLE_RATE);
}
struct ast_dsp *ast_dsp_new_with_rate(unsigned int sample_rate)
{
return __ast_dsp_new(sample_rate);
}
void ast_dsp_set_features(struct ast_dsp *dsp, int features)
{
dsp->features = features;
@ -1672,7 +1680,7 @@ int ast_dsp_set_digitmode(struct ast_dsp *dsp, int digitmode)
new = digitmode & (DSP_DIGITMODE_DTMF | DSP_DIGITMODE_MF | DSP_DIGITMODE_MUTECONF | DSP_DIGITMODE_MUTEMAX);
if (old != new) {
/* Must initialize structures if switching from MF to DTMF or vice-versa */
ast_digit_detect_init(&dsp->digit_state, new & DSP_DIGITMODE_MF);
ast_digit_detect_init(&dsp->digit_state, new & DSP_DIGITMODE_MF, dsp->sample_rate);
}
dsp->digitmode = digitmode;
return 0;

View File

@ -153,6 +153,7 @@ struct moh_files_state {
struct mohclass *class;
char name[MAX_MUSICCLASS];
struct ast_format origwfmt;
struct ast_format mohwfmt;
int samples;
int sample_queue;
int pos;
@ -267,6 +268,7 @@ static void moh_files_release(struct ast_channel *chan, void *data)
ast_verbose(VERBOSE_PREFIX_3 "Stopped music on hold on %s\n", chan->name);
}
ast_format_clear(&state->mohwfmt); /* make sure to clear this format before restoring the original format. */
if (state->origwfmt.id && ast_set_write_format(chan, &state->origwfmt)) {
ast_log(LOG_WARNING, "Unable to restore channel '%s' to format '%s'\n", chan->name, ast_getformatname(&state->origwfmt));
}
@ -355,6 +357,25 @@ static struct ast_frame *moh_files_readframe(struct ast_channel *chan)
return f;
}
static void moh_files_write_format_change(struct ast_channel *chan, void *data)
{
struct moh_files_state *state = chan->music_state;
/* In order to prevent a recursive call to this function as a result
* of setting the moh write format back on the channel. Clear
* the moh write format before setting the write format on the channel.*/
if (&state->origwfmt.id) {
struct ast_format tmp;
ast_format_copy(&tmp, &chan->writeformat);
if (state->mohwfmt.id) {
ast_format_clear(&state->origwfmt);
ast_set_write_format(chan, &state->mohwfmt);
}
ast_format_copy(&state->origwfmt, &tmp);
}
}
static int moh_files_generator(struct ast_channel *chan, void *data, int len, int samples)
{
struct moh_files_state *state = chan->music_state;
@ -375,6 +396,9 @@ static int moh_files_generator(struct ast_channel *chan, void *data, int len, in
ast_channel_unlock(chan);
state->samples += f->samples;
state->sample_queue -= f->samples;
if (ast_format_cmp(&f->subclass.format, &state->mohwfmt) == AST_FORMAT_CMP_NOT_EQUAL) {
ast_format_copy(&state->mohwfmt, &f->subclass.format);
}
res = ast_write(chan, f);
ast_frfree(f);
if (res < 0) {
@ -418,7 +442,9 @@ static void *moh_files_alloc(struct ast_channel *chan, void *params)
}
state->class = mohclass_ref(class, "Reffing music class for channel");
state->origwfmt = chan->writeformat;
ast_format_copy(&state->origwfmt, &chan->writeformat);
ast_format_copy(&state->mohwfmt, &chan->writeformat);
/* For comparison on restart of MOH (see above) */
ast_copy_string(state->name, class->name, sizeof(state->name));
state->save_total = class->total_files;
@ -462,6 +488,7 @@ static struct ast_generator moh_file_stream =
.release = moh_files_release,
.generate = moh_files_generator,
.digit = moh_handle_digit,
.write_format_change = moh_files_write_format_change,
};
static int spawn_mp3(struct mohclass *class)
@ -929,7 +956,7 @@ static void *moh_alloc(struct ast_channel *chan, void *params)
}
if ((res = mohalloc(class))) {
res->origwfmt = chan->writeformat;
ast_format_copy(&res->origwfmt, &chan->writeformat);
if (ast_set_write_format(chan, &class->format)) {
ast_log(LOG_WARNING, "Unable to set channel '%s' to format '%s'\n", chan->name, ast_codec2str(&class->format));
moh_release(NULL, res);