提交 ee5e2e09 authored 作者: Anthony Minessale's avatar Anthony Minessale

add a new software conference module (mod_conference)

git-svn-id: http://svn.freeswitch.org/svn/freeswitch/trunk@1770 d0543943-73ff-0310-b7d9-9358b9ac24b2
上级 204e6cf4
......@@ -7,7 +7,7 @@
<load module="mod_console"/>
<!-- <load module="mod_syslog"/> -->
<!-- XML Interfaces -->
<!-- XML Interfaces -->
<!-- <load module="mod_xml_rpc"/> -->
<!-- Event Handlers -->
......@@ -59,6 +59,10 @@
<!-- ASR /TTS -->
<!-- <load module="mod_cepstral"/> -->
<!-- <load module="mod_rss"/> -->
<!-- Conference Bridges -->
<!--<load module="mod_conference"/>-->
</modules>
</configuration>
......@@ -107,12 +111,12 @@
<param name="dialplan" value="XML"/>
<param name="dtmf-duration" value="100"/>
<param name="codec-prefs" value="PCMU,PCMA"/>
<!-- Payload number to bind DTMF to-->
<!-- Payload number to bind DTMF to-->
<param name="rfc2833-pt" value="101"/>
<!-- disable to trade async for more calls -->
<param name="use-rtp-timer" value="true"/>
<!-- auto sense NAT issues and adjust accordingly -->
<param name="use-rtp-auto-adjust" value="true"/>
<!-- disable to trade async for more calls -->
<param name="use-rtp-timer" value="true"/>
<!-- auto sense NAT issues and adjust accordingly -->
<param name="use-rtp-auto-adjust" value="true"/>
<!-- pick one (default if not specified is 'guess'); -->
<param name="rtp-ip" value="guess"/>
<!-- <param name-"rtp-ip" value="10.0.0.1"/> -->
......@@ -228,8 +232,8 @@
<param name="message" value="Jingle all the way"/>
<param name="rtp-ip" value="10.0.0.1"/>
<param name="auto-login" value="true"/>
<!-- disable to trade async for more calls -->
<param name="use-rtp-timer" value="true"/>
<!-- disable to trade async for more calls -->
<param name="use-rtp-timer" value="true"/>
<!-- or -->
<!-- <param name="rtp-ip" value="my_lan_ip"/> -->
<!-- <param name="ext-rtp-ip" value="stun:stun.server.com"/> -->
......@@ -243,16 +247,16 @@
</configuration>
<configuration name="xml_rpc.conf" description="XML RPC">
<settings>
<!-- The port where you want to run the http service (default 8080) -->
<param name="http-port" value="8080"/>
<!-- if all 3 of the following params exist all http traffic will require auth -->
<!-- The port where you want to run the http service (default 8080) -->
<param name="http-port" value="8080"/>
<!-- if all 3 of the following params exist all http traffic will require auth -->
<param name="auth-realm" value="freeswitch"/>
<param name="auth-user" value="freeswitch"/>
<param name="auth-pass" value="works"/>
<!-- The url to a gateway cgi that can generate xml similar to
what's in this file only on-the-fly (leave it commented if you dont
need it) -->
<!-- one or more |-delim of configuration|directory|dialplan -->
<!-- one or more |-delim of configuration|directory|dialplan -->
<!-- <param name="gateway-url" value="http://www.server.com/gateway.cgi" bindings="configuration"/> -->
</settings>
</configuration>
......@@ -265,6 +269,48 @@
</feeds>
</configuration>
<!-- None of these paths are real if you want any of these options
you need to really set them up -->
<configuration name="conference.conf" description="Audio Conference">
<!-- Profiles are collections of settings you can reference by name. -->
<profiles>
<profile name="default">
<!-- Sample Rate-->
<param name="rate" value="8000"/>
<!-- Number of milliseconds per frame -->
<param name="interval" value="20"/>
<!-- Energy level required for audio to be sent to the other users -->
<param name="energy_level" value="300"/>
<!-- File to play to acknowledge succees -->
<!--<param name="ack_sound" value="/soundfiles/beep.wav"/>-->
<!-- File to play to acknowledge failure -->
<!--<param name="nack_sound" value="/soundfiles/beeperr.wav"/>-->
<!-- File to play to acknowledge muted -->
<!--<param name="muted_sound" value="/soundfiles/muted.wav"/>-->
<!-- File to play to acknowledge unmuted -->
<!--<param name="unmuted_sound" value="/soundfiles/unmuted.wav"/>-->
<!-- File to play if you are alone in the conference -->
<!--<param name="alone_sound" value="/soundfiles/yactopitc.wav"/>-->
<!-- File to play when you join the conference -->
<!--<param name="enter_sound" value="/soundfiles/welcome.wav"/>-->
<!-- File to play when you leave the conference -->
<!--<param name="exit_sound" value="/soundfiles/exit.wav"/>-->
<!-- File to play when you ae ejected from the conference -->
<!--<param name="kicked_sound" value="/soundfiles/kicked.wav"/>-->
<!-- File to play when the conference is locked -->
<!--<param name="locked_sound" value="/soundfiles/locked.wav"/>-->
<!-- File to play to prompt for a pin -->
<!--<param name="pin_sound" value="/soundfiles/pin.wav"/>-->
<!-- File to play to when the pin is invalid -->
<!--<param name="bad_pin_sound" value="/soundfiles/invalid-pin.wav"/>-->
<!-- Conference pin -->
<!--<param name="pin" value="12345"/>-->
</profile>
</profiles>
<rooms>
<room name="freeswitch" profile="default"/>
</rooms>
</configuration>
</section>
<section name="dialplan" description="Regex/XML Dialplan">
<!-- Valid fields in conditions:
......@@ -274,17 +320,31 @@
<!-- *NOTE* The special context name 'any' will match any context -->
<context name="default">
<extension name="tollfree">
<condition field="destination_number" expression="^(18[0{2}8{2}7{2}6{2}]\d{7})">
<action application="bridge" data="exosip/$1-freeswitch@voip.trxtel.com"/>
</condition>
</extension>
<extension name="tollfree">
<condition field="destination_number" expression="^(18[0{2}8{2}7{2}6{2}]\d{7})">
<action application="bridge" data="exosip/$1-freeswitch@voip.trxtel.com"/>
</condition>
</extension>
<extension name="devconf">
<condition field="destination_number" expression="^888$">
<action application="bridge" data="exosip/888@66.250.68.194"/>
</condition>
</extension>
<extension name="devconf">
<condition field="destination_number" expression="^888$">
<action application="bridge" data="exosip/888@66.250.68.194"/>
</condition>
</extension>
<!-- Enter an existing conference -->
<extension name="1000">
<condition field="destination_number" expression="^1000$">
<action application="conference" data="1000"/>
</condition>
</extension>
<!-- Start a dynamic conference and call someone at the same time -->
<extension name="2000">
<condition field="destination_number" expression="^2000$">
<action application="conference" data="bridge:mydynaconf:exosip/1234@66.250.68.194"/>
</condition>
</extension>
<!-- if the destination is an exact match on the extension name
you do not need any regex in the condition -->
......@@ -294,30 +354,30 @@
<!-- extensions starting with 4, all the numbers after 4 form a numeric filename
continue=true means keep looking for more extensions to match
*NOTE* The entire dialplan is parsed ONCE when the call starts
so any call info acquired after the various actions cannot
be taken into consideration.
so any call info acquired after the various actions cannot
be taken into consideration.
The first match will play a beep and the second one plays
the desired file. This is for demo purposes both actions
could have been under the same <extension> tag as well.
-->
The first match will play a beep and the second one plays
the desired file. This is for demo purposes both actions
could have been under the same <extension> tag as well.
-->
<extension name="playsound1" continue="true">
<condition field="source" expression="mod_exosip"/>
<condition field="destination_number" expression="^4(\d+)">
<action application="playback" data="/var/sounds/beep.gsm"/>
</condition>
</condition>
</extension>
<extension name="playsound2">
<condition field="source" expression="mod_exosip"/>
<condition field="destination_number" expression="^4(\d+)">
<action application="playback" data="/root/$1.raw"/>
</condition>
</condition>
</extension>
<!-- send everything with a certian RDNIS to Wanpipe ISDN -->
<extension name="To PRI">
<condition field="rdnis" expression="8881231234"/>
<condition field="destination_number" expression="(.*)">
<action application="bridge" data="wanpipe/a/a/$1"/>
<action application="bridge" data="wanpipe/a/a/$1"/>
</condition>
</extension>
<!-- Call *MUST* originate from mod_iax and also be dialing ext 9999-->
......
/*
* FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
* Copyright (C) 2005/2006, Anthony Minessale II <anthmct@yahoo.com>
*
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
*
* The Initial Developer of the Original Code is
* Anthony Minessale II <anthmct@yahoo.com>
* Portions created by the Initial Developer are Copyright (C)
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Anthony Minessale II <anthmct@yahoo.com>
*
*
* mod_conference.c -- Software Conference Bridge
*
*/
#include <switch.h>
static const char modname[] = "mod_conference";
static const char global_app_name[] = "conference";
static char *global_cf_name = "conference.conf";
/* Size to allocate for audio buffers */
#define CONF_BUFFER_SIZE 1024 * 128
#define CONF_EVENT_MAINT "conference::maintenence"
#define CONF_DEFAULT_LEADIN 20
typedef enum {
FILE_STOP_CURRENT,
FILE_STOP_ALL
} file_stop_t;
/* Global Values */
static struct {
switch_memory_pool_t *conference_pool;
switch_mutex_t *conference_mutex;
switch_hash_t *conference_hash;
switch_mutex_t *id_mutex;
switch_mutex_t *hash_mutex;
uint32_t id_pool;
} globals;
struct conference_member;
struct conference_obj;
typedef struct conference_obj conference_obj_t;
typedef struct conference_member conference_member_t;
typedef enum {
MFLAG_RUNNING = (1 << 0),
MFLAG_CAN_SPEAK = (1 << 1),
MFLAG_CAN_HEAR = (1 << 2),
MFLAG_KICKED = (1 << 3),
MFLAG_ITHREAD = (1 << 4)
} member_flag_t;
typedef enum {
CFLAG_RUNNING = (1 << 0),
CFLAG_DYNAMIC = (1 << 1),
CFLAG_ENFORCE_MIN = (1 << 2),
CFLAG_DESTRUCT = (1 << 3),
CFLAG_LOCKED = (1 << 4)
} conf_flag_t;
typedef enum {
RFLAG_CAN_SPEAK = (1 << 0),
RFLAG_CAN_HEAR = (1 << 1)
} relation_flag_t;
typedef enum {
NODE_TYPE_FILE,
NODE_TYPE_SPEECH
} node_type_t;
struct confernce_file_node {
switch_file_handle_t fh;
switch_speech_handle_t sh;
node_type_t type;
uint8_t done;
switch_memory_pool_t *pool;
uint32_t leadin;
struct confernce_file_node *next;
};
typedef struct confernce_file_node confernce_file_node_t;
/* Conference Object */
struct conference_obj {
char *name;
char *timer_name;
char *tts_engine;
char *tts_voice;
char *enter_sound;
char *exit_sound;
char *alone_sound;
char *ack_sound;
char *nack_sound;
char *muted_sound;
char *unmuted_sound;
char *locked_sound;
char *kicked_sound;
char *caller_id_name;
char *caller_id_number;
char *pin;
char *pin_sound;
char *bad_pin_sound;
uint32_t flags;
switch_mutex_t *flag_mutex;
uint32_t rate;
uint32_t interval;
switch_mutex_t *mutex;
conference_member_t *members;
switch_mutex_t *member_mutex;
confernce_file_node_t *fnode;
switch_memory_pool_t *pool;
switch_thread_rwlock_t *rwlock;
uint32_t count;
uint32_t energy_level;
uint8_t min;
};
/* Relationship with another member */
struct conference_relationship {
uint32_t id;
uint32_t flags;
struct conference_relationship *next;
};
typedef struct conference_relationship conference_relationship_t;
/* Conference Member Object */
struct conference_member {
uint32_t id;
switch_core_session_t *session;
conference_obj_t *conference;
conference_obj_t *last_conference;
switch_memory_pool_t *pool;
switch_buffer_t *audio_buffer;
switch_buffer_t *mux_buffer;
uint32_t flags;
switch_mutex_t *flag_mutex;
switch_mutex_t *audio_in_mutex;
switch_mutex_t *audio_out_mutex;
switch_codec_t read_codec;
switch_codec_t write_codec;
uint8_t *frame;
uint8_t *mux_frame;
uint32_t buflen;
uint32_t read;
uint32_t len;
confernce_file_node_t *fnode;
conference_relationship_t *relationships;
struct conference_member *next;
};
/* Function Prototypes */
static uint32_t next_member_id(void);
static conference_relationship_t *member_get_relationship(conference_member_t *member, conference_member_t *other_member);
static conference_member_t *conference_member_get(conference_obj_t *conference, uint32_t id);
static conference_relationship_t *member_add_relationship(conference_member_t *member, uint32_t id);
static void member_del_relationship(conference_member_t *member, uint32_t id);
static switch_status_t conference_add_member(conference_obj_t *conference, conference_member_t *member);
static void conference_del_member(conference_obj_t *conference, conference_member_t *member);
static void *SWITCH_THREAD_FUNC conference_thread_run(switch_thread_t *thread, void *obj);
static void conference_loop(conference_member_t *member);
static uint32_t conference_stop_file(conference_obj_t *conference, file_stop_t stop);
static switch_status_t conference_play_file(conference_obj_t *conference, char *file, uint32_t leadin);
static switch_status_t conference_say(conference_obj_t *conference, char *text, uint32_t leadin);
static void conference_list(conference_obj_t *conference, switch_stream_handle_t *stream, char *delim);
static switch_status_t conf_function(char *buf, switch_stream_handle_t *stream);
static switch_status_t audio_bridge_on_ring(switch_core_session_t *session);
static switch_status_t conference_outcall(conference_obj_t *conference, switch_core_session_t *session, char *bridgeto, char *cid_name, char *cid_num);
static void conference_function(switch_core_session_t *session, char *data);
static void launch_conference_thread(conference_obj_t *conference);
static void *SWITCH_THREAD_FUNC input_thread_run(switch_thread_t *thread, void *obj);
static void launch_input_thread(conference_member_t *member, switch_memory_pool_t *pool);
static switch_status_t conference_local_play_file(switch_core_session_t *session, char *path, uint32_t leadin, char *buf, switch_size_t len);
static switch_status_t conference_member_play_file(conference_member_t *member, char *file, uint32_t leadin);
static switch_status_t conference_member_say(conference_obj_t *conference, conference_member_t *member, char *text, uint32_t leadin);
static uint32_t conference_member_stop_file(conference_member_t *member, file_stop_t stop);
static conference_obj_t *conference_new(char *name, switch_xml_t profile, switch_memory_pool_t *pool);
/* Return a Distinct ID # */
static uint32_t next_member_id(void)
{
uint32_t id;
switch_mutex_lock(globals.id_mutex);
id = ++globals.id_pool;
switch_mutex_unlock(globals.id_mutex);
return id;
}
/* if other_member has a relationship with member, produce it */
static conference_relationship_t *member_get_relationship(conference_member_t *member, conference_member_t *other_member)
{
conference_relationship_t *rel = NULL, *global = NULL;
switch_mutex_lock(member->flag_mutex);
switch_mutex_lock(other_member->flag_mutex);
if (member->relationships) {
for (rel = member->relationships; rel; rel = rel->next) {
if (rel->id == other_member->id) {
break;
}
/* 0 matches everyone.
(We will still test the others brcause a real match carries more clout) */
if (rel->id == 0) {
global = rel;
}
}
}
switch_mutex_unlock(other_member->flag_mutex);
switch_mutex_unlock(member->flag_mutex);
if (!rel && global) {
rel = global;
}
return rel;
}
static conference_member_t *conference_member_get(conference_obj_t *conference, uint32_t id)
{
conference_member_t *member = NULL;
for(member = conference->members; member; member = member->next) {
if (member->id == id) {
break;
}
}
return member;
}
/* Add a custom relationship to a member */
static conference_relationship_t *member_add_relationship(conference_member_t *member, uint32_t id)
{
conference_relationship_t *rel = NULL;
if ((rel = switch_core_alloc(member->pool, sizeof(*rel)))) {
rel->id = id;
switch_mutex_lock(member->flag_mutex);
rel->next = member->relationships;
member->relationships = rel;
switch_mutex_unlock(member->flag_mutex);
}
return rel;
}
/* Remove a custom relationship from a member */
static void member_del_relationship(conference_member_t *member, uint32_t id)
{
conference_relationship_t *rel, *last = NULL;
switch_mutex_lock(member->flag_mutex);
for (rel = member->relationships; rel; rel = rel->next) {
if (rel->id == id) {
/* we just forget about rel here cos it was allocated by the member's pool
it will be freed when the member is */
if (last) {
last->next = rel->next;
} else {
member->relationships = rel->next;
}
}
last = rel;
}
switch_mutex_unlock(member->flag_mutex);
}
/* Gain exclusive access and add the member to the list */
static switch_status_t conference_add_member(conference_obj_t *conference, conference_member_t *member)
{
switch_event_t *event;
if (switch_thread_rwlock_tryrdlock(conference->rwlock) != SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Read Lock Fail\n");
return SWITCH_STATUS_FALSE;
}
switch_mutex_lock(conference->mutex);
switch_mutex_lock(conference->member_mutex);
switch_mutex_lock(member->audio_in_mutex);
switch_mutex_lock(member->audio_out_mutex);
switch_mutex_lock(member->flag_mutex);
member->conference = member->last_conference = conference;
member->next = conference->members;
conference->members = member;
conference->count++;
if (conference->enter_sound) {
conference_play_file(conference, conference->enter_sound, CONF_DEFAULT_LEADIN);
}
if (conference->count == 1 && conference->alone_sound) {
conference_play_file(conference, conference->alone_sound, CONF_DEFAULT_LEADIN);
}
if (conference->min && conference->count >= conference->min) {
switch_set_flag(conference, CFLAG_ENFORCE_MIN);
}
if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
switch_channel_t *channel = switch_core_session_get_channel(member->session);
switch_channel_event_set_data(channel, event);
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Conference-Name", conference->name);
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Action", "add-member");
switch_event_fire(&event);
}
switch_mutex_unlock(member->flag_mutex);
switch_mutex_unlock(member->audio_out_mutex);
switch_mutex_unlock(member->audio_in_mutex);
switch_mutex_unlock(conference->member_mutex);
switch_mutex_unlock(conference->mutex);
return SWITCH_STATUS_SUCCESS;
}
/* Gain exclusive access and remove the member from the list */
static void conference_del_member(conference_obj_t *conference, conference_member_t *member)
{
conference_member_t *imember, *last = NULL;
switch_event_t *event;
switch_mutex_lock(conference->mutex);
switch_mutex_lock(conference->member_mutex);
switch_mutex_lock(member->audio_in_mutex);
switch_mutex_lock(member->audio_out_mutex);
switch_mutex_lock(member->flag_mutex);
for (imember = conference->members; imember; imember = imember->next) {
if (imember == member ) {
if (last) {
last->next = imember->next;
} else {
conference->members = imember->next;
}
break;
}
last = imember;
}
conference->count--;
member->conference = NULL;
if (conference->min && switch_test_flag(conference, CFLAG_ENFORCE_MIN) && conference->count < conference->min) {
switch_set_flag(conference, CFLAG_DESTRUCT);
} else {
if (conference->exit_sound) {
conference_play_file(conference, conference->exit_sound, 0);
}
if (conference->count == 1 && conference->alone_sound) {
conference_play_file(conference, conference->alone_sound, 0);
}
}
if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
switch_channel_t *channel = switch_core_session_get_channel(member->session);
switch_channel_event_set_data(channel, event);
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Conference-Name", conference->name);
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Action", "del-member");
switch_event_fire(&event);
}
switch_mutex_unlock(member->flag_mutex);
switch_mutex_unlock(member->audio_out_mutex);
switch_mutex_unlock(member->audio_in_mutex);
switch_mutex_unlock(conference->member_mutex);
switch_mutex_unlock(conference->mutex);
switch_thread_rwlock_unlock(conference->rwlock);
}
/* Main monitor thread (1 per distinct conference room) */
static void *SWITCH_THREAD_FUNC conference_thread_run(switch_thread_t *thread, void *obj)
{
conference_obj_t *conference = (conference_obj_t *) obj;
conference_member_t *imember, *omember;
uint32_t divider = 1000 / conference->interval;
uint32_t samples = (conference->rate / divider);
uint32_t bytes = samples * 2;
uint8_t ready = 0;
switch_timer_t timer = {0};
if (switch_core_timer_init(&timer, conference->timer_name, conference->interval, samples, conference->pool) == SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "setup timer success interval: %u samples: %u\n", conference->interval, samples);
} else {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Timer Setup Failed. Conference Cannot Start\n");
return NULL;
}
while(!switch_test_flag(conference, CFLAG_DESTRUCT)) {
uint8_t file_frame[CONF_BUFFER_SIZE] = {0};
switch_size_t file_sample_len = samples;
switch_size_t file_data_len = samples * 2;
/* Sync the conference to a single timing source */
switch_core_timer_next(&timer);
switch_mutex_lock(conference->mutex);
ready = 0;
/* Read one frame of audio from each member channel and save it for redistribution */
for (imember = conference->members; imember; imember = imember->next) {
if (imember->buflen) {
memset(imember->frame, 255, bytes);
} else {
imember->frame = switch_core_alloc(imember->pool, bytes);
imember->mux_frame = switch_core_alloc(imember->pool, bytes);
imember->buflen = bytes;
}
switch_mutex_lock(imember->audio_in_mutex);
if ((imember->read = (uint32_t)switch_buffer_read(imember->audio_buffer, imember->frame, imember->buflen))) {
ready++; /* Tally of how many channels had data */
}
switch_mutex_unlock(imember->audio_in_mutex);
}
/* If a file or speech event is being played */
if (conference->fnode) {
/* Lead in time */
if (conference->fnode->leadin) {
conference->fnode->leadin--;
} else {
ready++;
if (conference->fnode->type == NODE_TYPE_SPEECH) {
switch_speech_flag_t flags = SWITCH_SPEECH_FLAG_BLOCKING;
uint32_t rate = conference->rate;
if (switch_core_speech_read_tts(&conference->fnode->sh,
file_frame,
&file_data_len,
&rate,
&flags) == SWITCH_STATUS_SUCCESS) {
file_sample_len = file_data_len / 2;
} else {
file_sample_len = file_data_len = 0;
}
} else if (conference->fnode->type == NODE_TYPE_FILE) {
switch_core_file_read(&conference->fnode->fh, file_frame, &file_sample_len);
}
if (file_sample_len <= 0) {
conference->fnode->done++;
}
}
}
if (ready) {
/* Build a muxed frame for every member that contains the mixed audio of everyone else */
for (omember = conference->members; omember; omember = omember->next) {
if (conference->fnode) {
memcpy(omember->mux_frame, file_frame, file_sample_len * 2);
omember->len = bytes;
} else {
memset(omember->mux_frame, 255, bytes);
}
for (imember = conference->members; imember; imember = imember->next) {
if (imember == omember) {
/* Don't add audio from yourself */
continue;
}
if (imember->read) { /* mux the frame with the collective */
uint32_t x;
int16_t *bptr, *muxed;
/* If they are not supposed to talk to us then don't let them */
if (omember->relationships) {
conference_relationship_t *rel;
if ((rel = member_get_relationship(omember, imember))) {
if (! switch_test_flag(rel, RFLAG_CAN_HEAR)) {
continue;
}
}
}
/* If we are not supposed to hear them then don't let it happen */
if (imember->relationships) {
conference_relationship_t *rel;
if ((rel = member_get_relationship(imember, omember))) {
if (! switch_test_flag(rel, RFLAG_CAN_SPEAK)) {
continue;
}
}
}
if (imember->read > imember->len) {
imember->len = imember->read;
}
bptr = (int16_t *) imember->frame;
muxed = (int16_t *) omember->mux_frame;
for (x = 0; x < imember->read / 2; x++) {
muxed[x] = muxed[x] + bptr[x];
}
ready++;
}
}
}
/* Go back and write each member his dedicated copy of the audio frame that does not contain his own audio. */
for (imember = conference->members; imember; imember = imember->next) {
switch_mutex_lock(imember->audio_out_mutex);
switch_buffer_write(imember->mux_buffer, imember->mux_frame, imember->len);
switch_mutex_unlock(imember->audio_out_mutex);
}
}
if (conference->fnode && conference->fnode->done) {
confernce_file_node_t *fnode;
switch_memory_pool_t *pool;
if (conference->fnode->type == NODE_TYPE_SPEECH) {
switch_speech_flag_t flags = SWITCH_SPEECH_FLAG_TTS;
switch_core_speech_close(&conference->fnode->sh, &flags);
} else {
switch_core_file_close(&conference->fnode->fh);
}
fnode = conference->fnode;
conference->fnode = conference->fnode->next;
pool = fnode->pool;
fnode = NULL;
switch_core_destroy_memory_pool(&pool);
}
switch_mutex_unlock(conference->mutex);
} /* Rinse ... Repeat */
switch_core_timer_destroy(&timer);
if (switch_test_flag(conference, CFLAG_DESTRUCT)) {
switch_mutex_lock(conference->mutex);
for(imember = conference->members; imember; imember = imember->next) {
switch_channel_t *channel = switch_core_session_get_channel(imember->session);
switch_channel_hangup(channel, SWITCH_CAUSE_NORMAL_CLEARING);
switch_clear_flag_locked(imember, MFLAG_RUNNING);
}
switch_mutex_unlock(conference->mutex);
/* Wait till everybody is out */
switch_thread_rwlock_wrlock(conference->rwlock);
switch_thread_rwlock_unlock(conference->rwlock);
switch_mutex_lock(globals.hash_mutex);
switch_core_hash_delete(globals.conference_hash, conference->name);
switch_mutex_unlock(globals.hash_mutex);
if (conference->pool) {
switch_memory_pool_t *pool = conference->pool;
switch_core_destroy_memory_pool(&pool);
}
}
return NULL;
}
/* Sub-Routine called by a channel inside a conference */
static void conference_loop(conference_member_t *member)
{
switch_channel_t *channel;
switch_frame_t write_frame = {0};
uint8_t data[SWITCH_RECCOMMENDED_BUFFER_SIZE];
switch_timer_t timer = {0};
uint32_t divider = 1000 / member->conference->interval;
uint32_t samples = (member->conference->rate / divider);
uint32_t bytes = samples * 2;
channel = switch_core_session_get_channel(member->session);
assert(channel != NULL);
assert(member->conference != NULL);
if (switch_core_timer_init(&timer,
member->conference->timer_name,
member->conference->interval,
samples,
NULL) == SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "XXXsetup timer %s success interval: %u samples: %u\n",
member->conference->timer_name, member->conference->interval, samples);
} else {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Timer Setup Failed. Conference Cannot Start\n");
return;
}
/* Answer the channel */
switch_channel_answer(channel);
/* Prepare the write frame */
write_frame.data = data;
write_frame.buflen = sizeof(data);
write_frame.codec = &member->write_codec;
/* Start a thread to read data and feed it into the buffer and use this thread to generate output */
launch_input_thread(member, switch_core_session_get_pool(member->session));
while(switch_test_flag(member, MFLAG_RUNNING) && switch_test_flag(member, MFLAG_ITHREAD) && switch_channel_ready(channel)) {
char dtmf[128] = "";
uint8_t file_frame[CONF_BUFFER_SIZE] = {0};
switch_size_t file_data_len = samples * 2;
switch_size_t file_sample_len = samples;
char *digit;
switch_core_timer_next(&timer);
if (switch_channel_has_dtmf(channel)) {
switch_channel_dequeue_dtmf(channel, dtmf, sizeof(dtmf));
}
for (digit = dtmf; *digit; digit++) {
switch(*digit) {
case '1':
if (switch_test_flag(member, MFLAG_CAN_SPEAK)) {
switch_clear_flag_locked(member, MFLAG_CAN_SPEAK);
if (member->conference->muted_sound) {
conference_member_play_file(member, member->conference->muted_sound, 0);
}
} else {
switch_set_flag_locked(member, MFLAG_CAN_SPEAK);
if (member->conference->unmuted_sound) {
conference_member_play_file(member, member->conference->unmuted_sound, 0);
}
}
break;
case '#':
switch_clear_flag_locked(member, MFLAG_RUNNING);
break;
default:
break;
}
}
if (member->fnode) {
if (member->fnode->done) {
confernce_file_node_t *fnode;
switch_memory_pool_t *pool;
if (member->fnode->type == NODE_TYPE_SPEECH) {
switch_speech_flag_t flags = SWITCH_SPEECH_FLAG_TTS;
switch_core_speech_close(&member->fnode->sh, &flags);
} else {
switch_core_file_close(&member->fnode->fh);
}
switch_mutex_lock(member->flag_mutex);
fnode = member->fnode;
member->fnode = member->fnode->next;
switch_mutex_unlock(member->flag_mutex);
pool = fnode->pool;
fnode = NULL;
switch_core_destroy_memory_pool(&pool);
} else {
if (member->fnode->leadin) {
member->fnode->leadin--;
} else {
if (member->fnode->type == NODE_TYPE_SPEECH) {
switch_speech_flag_t flags = SWITCH_SPEECH_FLAG_BLOCKING;
uint32_t rate = member->conference->rate;
if (switch_core_speech_read_tts(&member->fnode->sh,
file_frame,
&file_data_len,
&rate,
&flags) == SWITCH_STATUS_SUCCESS) {
file_sample_len = file_data_len / 2;
} else {
file_sample_len = file_data_len = 0;
}
} else if (member->fnode->type == NODE_TYPE_FILE) {
switch_core_file_read(&member->fnode->fh, file_frame, &file_sample_len);
file_data_len = file_sample_len * 2;
}
if (file_sample_len <= 0) {
member->fnode->done++;
} else { /* there is file node data to deliver */
write_frame.data = file_frame;
write_frame.datalen = (uint32_t)file_data_len;
write_frame.samples = (uint32_t)file_sample_len;
switch_core_session_write_frame(member->session, &write_frame, -1, 0);
/* forget the conference data we played file node data instead */
switch_mutex_lock(member->audio_out_mutex);
switch_buffer_zero(member->mux_buffer);
switch_mutex_unlock(member->audio_out_mutex);
}
}
}
} else {
/* Flush the output buffer and write all the data (presumably muxed) back to the channel */
switch_mutex_lock(member->audio_out_mutex);
write_frame.data = data;
while ((write_frame.datalen = (uint32_t)switch_buffer_read(member->mux_buffer, write_frame.data, bytes))) {
if (write_frame.datalen && switch_test_flag(member, MFLAG_CAN_HEAR)) {
write_frame.samples = write_frame.datalen / 2;
switch_core_session_write_frame(member->session, &write_frame, -1, 0);
}
}
switch_mutex_unlock(member->audio_out_mutex);
}
} /* Rinse ... Repeat */
switch_clear_flag_locked(member, MFLAG_RUNNING);
switch_core_timer_destroy(&timer);
/* Wait for the input thead to end */
while(switch_test_flag(member, MFLAG_ITHREAD)) {
switch_yield(1000);
}
}
/* Make files stop playing in a conference either the current one or all of them */
static uint32_t conference_stop_file(conference_obj_t *conference, file_stop_t stop)
{
confernce_file_node_t *nptr;
uint32_t count = 0;
switch_mutex_lock(conference->mutex);
if (stop == FILE_STOP_ALL) {
for (nptr = conference->fnode; nptr; nptr = nptr->next) {
nptr->done++;
count++;
}
} else {
if (conference->fnode) {
conference->fnode->done++;
count++;
}
}
switch_mutex_unlock(conference->mutex);
return count;
}
static uint32_t conference_member_stop_file(conference_member_t *member, file_stop_t stop)
{
confernce_file_node_t *nptr;
uint32_t count = 0;
switch_mutex_lock(member->flag_mutex);
if (stop == FILE_STOP_ALL) {
for (nptr = member->fnode; nptr; nptr = nptr->next) {
nptr->done++;
count++;
}
} else {
if (member->fnode) {
member->fnode->done++;
count++;
}
}
switch_mutex_unlock(member->flag_mutex);
return count;
}
/* Play a file in the conference rooom */
static switch_status_t conference_play_file(conference_obj_t *conference, char *file, uint32_t leadin)
{
confernce_file_node_t *fnode, *nptr;
switch_memory_pool_t *pool;
/* Setup a memory pool to use. */
if (switch_core_new_memory_pool(&pool) != SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Pool Failure\n");
return SWITCH_STATUS_MEMERR;
}
/* Create a node object*/
if (!(fnode = switch_core_alloc(pool, sizeof(*fnode)))) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Alloc Failure\n");
switch_core_destroy_memory_pool(&pool);
return SWITCH_STATUS_MEMERR;
}
fnode->type = NODE_TYPE_FILE;
fnode->leadin = leadin;
/* Open the file */
if (switch_core_file_open(&fnode->fh,
file,
SWITCH_FILE_FLAG_READ | SWITCH_FILE_DATA_SHORT,
pool) != SWITCH_STATUS_SUCCESS) {
switch_core_destroy_memory_pool(&pool);
return SWITCH_STATUS_NOTFOUND;
}
fnode->pool = pool;
/* Queue the node */
switch_mutex_lock(conference->mutex);
for (nptr = conference->fnode; nptr && nptr->next; nptr = nptr->next);
if (nptr) {
nptr->next = fnode;
} else {
conference->fnode = fnode;
}
switch_mutex_unlock(conference->mutex);
return SWITCH_STATUS_SUCCESS;
}
/* Play a file in the conference rooom to a member */
static switch_status_t conference_member_play_file(conference_member_t *member, char *file, uint32_t leadin)
{
confernce_file_node_t *fnode, *nptr;
switch_memory_pool_t *pool;
/* Setup a memory pool to use. */
if (switch_core_new_memory_pool(&pool) != SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Pool Failure\n");
return SWITCH_STATUS_MEMERR;
}
/* Create a node object*/
if (!(fnode = switch_core_alloc(pool, sizeof(*fnode)))) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Alloc Failure\n");
switch_core_destroy_memory_pool(&pool);
return SWITCH_STATUS_MEMERR;
}
fnode->type = NODE_TYPE_FILE;
fnode->leadin = leadin;
/* Open the file */
if (switch_core_file_open(&fnode->fh,
file,
SWITCH_FILE_FLAG_READ | SWITCH_FILE_DATA_SHORT,
pool) != SWITCH_STATUS_SUCCESS) {
switch_core_destroy_memory_pool(&pool);
return SWITCH_STATUS_NOTFOUND;
}
fnode->pool = pool;
/* Queue the node */
switch_mutex_lock(member->flag_mutex);
for (nptr = member->fnode; nptr && nptr->next; nptr = nptr->next);
if (nptr) {
nptr->next = fnode;
} else {
member->fnode = fnode;
}
switch_mutex_unlock(member->flag_mutex);
return SWITCH_STATUS_SUCCESS;
}
/* Say some thing with TTS in the conference rooom */
static switch_status_t conference_member_say(conference_obj_t *conference, conference_member_t *member, char *text, uint32_t leadin)
{
confernce_file_node_t *fnode, *nptr;
switch_memory_pool_t *pool;
switch_speech_flag_t flags = SWITCH_SPEECH_FLAG_TTS;
/* Setup a memory pool to use. */
if (switch_core_new_memory_pool(&pool) != SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Pool Failure\n");
return SWITCH_STATUS_MEMERR;
}
/* Create a node object*/
if (!(fnode = switch_core_alloc(pool, sizeof(*fnode)))) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Alloc Failure\n");
switch_core_destroy_memory_pool(&pool);
return SWITCH_STATUS_MEMERR;
}
fnode->type = NODE_TYPE_SPEECH;
fnode->leadin = leadin;
memset(&fnode->sh, 0, sizeof(fnode->sh));
if (switch_core_speech_open(&fnode->sh,
conference->tts_engine,
conference->tts_voice,
conference->rate,
&flags,
conference->pool) != SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Invalid TTS module [%s]!\n", conference->tts_engine);
return SWITCH_STATUS_FALSE;
}
fnode->pool = pool;
/* Queue the node */
switch_mutex_lock(member->flag_mutex);
for (nptr = member->fnode; nptr && nptr->next; nptr = nptr->next);
if (nptr) {
nptr->next = fnode;
} else {
member->fnode = fnode;
}
switch_mutex_unlock(member->flag_mutex);
/* Begin Generation */
switch_sleep(200000);
switch_core_speech_feed_tts(&fnode->sh, text, &flags);
return SWITCH_STATUS_SUCCESS;
}
/* Say some thing with TTS in the conference rooom */
static switch_status_t conference_say(conference_obj_t *conference, char *text, uint32_t leadin)
{
confernce_file_node_t *fnode, *nptr;
switch_memory_pool_t *pool;
switch_speech_flag_t flags = SWITCH_SPEECH_FLAG_TTS;
/* Setup a memory pool to use. */
if (switch_core_new_memory_pool(&pool) != SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Pool Failure\n");
return SWITCH_STATUS_MEMERR;
}
/* Create a node object*/
if (!(fnode = switch_core_alloc(pool, sizeof(*fnode)))) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Alloc Failure\n");
switch_core_destroy_memory_pool(&pool);
return SWITCH_STATUS_MEMERR;
}
fnode->type = NODE_TYPE_SPEECH;
fnode->leadin = leadin;
memset(&fnode->sh, 0, sizeof(fnode->sh));
if (switch_core_speech_open(&fnode->sh,
conference->tts_engine,
conference->tts_voice,
conference->rate,
&flags,
conference->pool) != SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Invalid TTS module [%s]!\n", conference->tts_engine);
return SWITCH_STATUS_FALSE;
}
fnode->pool = pool;
/* Queue the node */
switch_mutex_lock(conference->mutex);
for (nptr = conference->fnode; nptr && nptr->next; nptr = nptr->next);
if (nptr) {
nptr->next = fnode;
} else {
conference->fnode = fnode;
}
switch_mutex_unlock(conference->mutex);
/* Begin Generation */
switch_sleep(200000);
switch_core_speech_feed_tts(&fnode->sh, text, &flags);
return SWITCH_STATUS_SUCCESS;
}
static void conference_list(conference_obj_t *conference, switch_stream_handle_t *stream, char *delim)
{
conference_member_t *member = NULL;
switch_mutex_lock(conference->member_mutex);
for (member = conference->members; member; member = member->next) {
switch_channel_t *channel = switch_core_session_get_channel(member->session);
switch_caller_profile_t *profile = switch_channel_get_caller_profile(channel);
char *uuid = switch_core_session_get_uuid(member->session);
char *name = switch_channel_get_name(channel);
uint32_t count = 0;
stream->write_function(stream, "%u%s%s%s%s%s%s%s%s%s",
member->id,delim,
name,delim,
uuid,delim,
profile->caller_id_name,delim,
profile->caller_id_number, delim);
if (switch_test_flag(member, MFLAG_CAN_HEAR)) {
stream->write_function(stream, "hear");
count++;
}
if (switch_test_flag(member, MFLAG_CAN_SPEAK)) {
stream->write_function(stream, "%s%s", count ? "|" : "", "speak");
count++;
}
stream->write_function(stream, "\n");
}
switch_mutex_unlock(conference->member_mutex);
}
/* API Interface Function */
static switch_status_t conf_function(char *buf, switch_stream_handle_t *stream)
{
char *lbuf = NULL;
switch_status_t status = SWITCH_STATUS_SUCCESS;
char *http = NULL;
char *topusage = "Available Commands:\n"
"--------------------------------------------------------------------------------\n"
"conference commands\n"
"conference list [delim <string>]\n"
"conference <confname> list [delim <string>]\n"
"conference <confname> play <file_path> [<member_id>]\n"
"conference <confname> say <text>\n"
"conference <confname> saymember <member_id><text>\n"
"conference <confname> stop <[current|all]> [<member_id>]\n"
"conference <confname> kick <member_id>\n"
"conference <confname> mute <member_id>\n"
"conference <confname> unmute <member_id>\n"
"conference <confname> deaf <member_id>\n"
"conference <confname> undef <member_id>\n"
"conference <confname> relate <member_id> <other_member_id> [nospeak|nohear]\n"
"conference <confname> lock\n"
"conference <confname> unlock\n"
"conference <confname> dial <endpoint_module_name>/<destination>\n"
"conference <confname> transfer <member_id> <conference_name>\n"
;
if (stream->event) {
http = switch_event_get_header(stream->event, "http-host");
}
if (http) {
/* Output must be to a web browser */
stream->write_function(stream, "<pre>\n");
}
if (!buf) {
stream->write_function(stream, topusage);
return status;
}
if ((lbuf = strdup(buf))) {
conference_obj_t *conference = NULL;
int argc;
char *argv[25];
switch_event_t *event;
argc = switch_separate_string(lbuf, ' ', argv, (sizeof(argv) / sizeof(argv[0])));
/* Figure out what conference */
if (argc) {
if (!strcasecmp(argv[0], "commands")) {
stream->write_function(stream, topusage);
goto done;
} else if (!strcasecmp(argv[0], "list")) {
switch_hash_index_t *hi;
void *val;
char *d = ";";
if (argv[1]) {
if (argv[2] && !strcasecmp(argv[1], "delim")) {
d = argv[2];
if (*d == '"') {
if (++d) {
char *p;
if ((p = strchr(d, '"'))) {
*p = '\0';
}
} else {
d = ";";
}
}
}
}
for (hi = switch_hash_first(globals.conference_pool, globals.conference_hash); hi; hi = switch_hash_next(hi)) {
switch_hash_this(hi, NULL, NULL, &val);
conference = (conference_obj_t *) val;
stream->write_function(stream, "Conference %s (%u members)\n", conference->name, conference->count);
conference_list(conference, stream, d);
stream->write_function(stream, "\n");
}
goto done;
} else if (!(conference = (conference_obj_t *) switch_core_hash_find(globals.conference_hash, argv[0]))) {
stream->write_function(stream, "No Conference called %s found.\n", argv[0]);
goto done;
}
if (argc > 1) {
if (!strcasecmp(argv[1], "lock")) {
switch_set_flag_locked(conference, CFLAG_LOCKED);
stream->write_function(stream, "OK %s locked\n", argv[0]);
if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Conference-Name", conference->name);
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Action", "lock");
switch_event_fire(&event);
}
goto done;
} else if (!strcasecmp(argv[1], "unlock")) {
switch_clear_flag_locked(conference, CFLAG_LOCKED);
stream->write_function(stream, "OK %s unlocked\n", argv[0]);
if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Conference-Name", conference->name);
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Action", "lock");
switch_event_fire(&event);
}
goto done;
} else if (!strcasecmp(argv[1], "dial")) {
if (argc > 2) {
conference_outcall(conference, NULL, argv[2], argv[3], argv[4]);
stream->write_function(stream, "OK\n");
goto done;
} else {
stream->write_function(stream, "Error!\n");
goto done;
}
} else if (!strcasecmp(argv[1], "play")) {
if (argc == 3) {
if (conference_play_file(conference, argv[2], 0) == SWITCH_STATUS_SUCCESS) {
stream->write_function(stream, "(play) Playing file %s\n", argv[2]);
if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Conference-Name", conference->name);
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Action", "play-file");
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "File", argv[2]);
switch_event_fire(&event);
}
goto done;
} else {
stream->write_function(stream, "(play) File: %s not found.\n", argv[2] ? argv[2] : "(unspecified)");
goto done;
}
} else if (argc == 4) {
uint32_t id = atoi(argv[3]);
conference_member_t *member;
if ((member = conference_member_get(conference, id))) {
if (conference_member_play_file(member, argv[2], 0) == SWITCH_STATUS_SUCCESS) {
stream->write_function(stream, "(play) Playing file %s to member %u\n", argv[2], id);
if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Conference-Name", conference->name);
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Member-ID", "%u", id);
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Action", "play-file-member");
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "File", argv[2]);
switch_event_fire(&event);
}
goto done;
} else {
stream->write_function(stream, "(play) File: %s not found.\n", argv[2] ? argv[2] : "(unspecified)");
goto done;
}
} else {
stream->write_function(stream, "Member: %u not found.\n", id);
goto done;
}
}
} else if (!strcasecmp(argv[1], "say")) {
char *tbuf = NULL;
char *text;
if ((tbuf = strdup(buf))) {
if ((text = strstr(tbuf, "say "))) {
text += 4;
if (text && conference_say(conference, text, 0) == SWITCH_STATUS_SUCCESS) {
stream->write_function(stream, "(say) OK\n");
if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Conference-Name", conference->name);
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Action", "speak-text");
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Text", text);
switch_event_fire(&event);
}
goto done;
} else {
stream->write_function(stream, "(say) Error!");
goto done;
}
}
free(tbuf);
}
} else if (!strcasecmp(argv[1], "saymember")) {
char *tbuf = NULL, *text, *name;
uint32_t id = atoi(argv[3]);
conference_member_t *member;
if ((tbuf = strdup(buf))) {
if ((name = strstr(tbuf, "saymember "))) {
name += 10;
text = strchr(name, ' ');
id = atoi(name);
if ((member = conference_member_get(conference, id))) {
if (text && conference_member_say(conference, member, text, 0) == SWITCH_STATUS_SUCCESS) {
stream->write_function(stream, "(saymember) OK\n");
if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Conference-Name", conference->name);
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Action", "speak-text-member");
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Member-ID", "%u", id);
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Text", text);
switch_event_fire(&event);
}
} else {
stream->write_function(stream, "(saymember) Error!");
}
} else {
stream->write_function(stream, "(saymember) Unknown Member %u!", id);
}
} else {
stream->write_function(stream, "(saymember) Syntax Error!");
}
free(tbuf);
goto done;
}
} else if (!strcasecmp(argv[1], "stop")) {
uint8_t current = 0, all = 0;
if (argc > 2) {
current = strcasecmp(argv[2], "current") ? 0 : 1;
all = strcasecmp(argv[2], "all") ? 0 : 1;
}
if (current || all) {
if (argc == 4) {
uint32_t id = atoi(argv[3]);
conference_member_t *member;
if ((member = conference_member_get(conference, id))) {
uint32_t stopped = conference_member_stop_file(member, current ? FILE_STOP_CURRENT : FILE_STOP_ALL);
stream->write_function(stream, "Stopped %u files.\n", stopped);
} else {
stream->write_function(stream, "Member: %u not found.\n", id);
goto done;
}
} else {
uint32_t stopped = conference_stop_file(conference, current ? FILE_STOP_CURRENT : FILE_STOP_ALL);
stream->write_function(stream, "Stopped %u files.\n", stopped);
}
} else {
stream->write_function(stream, "Usage stop [current/all]\n");
goto done;
}
} else if (!strcasecmp(argv[1], "mute")) {
if (argc > 2) {
uint32_t id = atoi(argv[2]);
conference_member_t *member;
if ((member = conference_member_get(conference, id))) {
switch_clear_flag_locked(member, MFLAG_CAN_SPEAK);
if (member->conference->muted_sound) {
conference_member_play_file(member, member->conference->muted_sound, 0);
}
stream->write_function(stream, "OK mute %u\n", id);
if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
switch_channel_t *channel = switch_core_session_get_channel(member->session);
switch_channel_event_set_data(channel, event);
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Conference-Name", conference->name);
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Member-ID", "%u", id);
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Action", "mute-member");
switch_event_fire(&event);
}
goto done;
} else {
stream->write_function(stream, "Non-Existant ID %u\n", id);
goto done;
}
} else {
stream->write_function(stream, "usage mute <id>\n");
goto done;
}
} else if (!strcasecmp(argv[1], "unmute")) {
if (argc > 2) {
uint32_t id = atoi(argv[2]);
conference_member_t *member;
if ((member = conference_member_get(conference, id))) {
switch_set_flag_locked(member, MFLAG_CAN_SPEAK);
stream->write_function(stream, "OK unmute %u\n", id);
if (member->conference->unmuted_sound) {
conference_member_play_file(member, member->conference->unmuted_sound, 0);
}
if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
switch_channel_t *channel = switch_core_session_get_channel(member->session);
switch_channel_event_set_data(channel, event);
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Conference-Name", conference->name);
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Member-ID", "%u", id);
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Action", "unmute-member");
switch_event_fire(&event);
}
goto done;
} else {
stream->write_function(stream, "Non-Existant ID %u\n", id);
goto done;
}
} else {
stream->write_function(stream, "usage unmute <id>\n");
goto done;
}
} else if (!strcasecmp(argv[1], "deaf")) {
if (argc > 2) {
uint32_t id = atoi(argv[2]);
conference_member_t *member;
if ((member = conference_member_get(conference, id))) {
switch_clear_flag_locked(member, MFLAG_CAN_HEAR);
stream->write_function(stream, "OK deaf %u\n", id);
if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
switch_channel_t *channel = switch_core_session_get_channel(member->session);
switch_channel_event_set_data(channel, event);
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Conference-Name", conference->name);
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Member-ID", "%u", id);
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Action", "deaf-member");
switch_event_fire(&event);
}
goto done;
} else {
stream->write_function(stream, "Non-Existant ID %u\n", id);
goto done;
}
} else {
stream->write_function(stream, "usage deaf <id>\n");
goto done;
}
} else if (!strcasecmp(argv[1], "undeaf")) {
if (argc > 2) {
uint32_t id = atoi(argv[2]);
conference_member_t *member;
if ((member = conference_member_get(conference, id))) {
switch_set_flag_locked(member, MFLAG_CAN_HEAR);
stream->write_function(stream, "OK undeaf %u\n", id);
if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
switch_channel_t *channel = switch_core_session_get_channel(member->session);
switch_channel_event_set_data(channel, event);
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Conference-Name", conference->name);
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Member-ID", "%u", id);
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Action", "undeaf-member");
switch_event_fire(&event);
}
goto done;
} else {
stream->write_function(stream, "Non-Existant ID %u\n", id);
goto done;
}
} else {
stream->write_function(stream, "usage undeaf <id>\n");
goto done;
}
} else if (!strcasecmp(argv[1], "kick")) {
if (argc > 2) {
uint32_t id = atoi(argv[2]);
conference_member_t *member;
if ((member = conference_member_get(conference, id))) {
switch_mutex_lock(member->flag_mutex);
switch_clear_flag(member, MFLAG_RUNNING);
switch_set_flag(member, MFLAG_KICKED);
switch_mutex_unlock(member->flag_mutex);
stream->write_function(stream, "OK kicked %u\n", id);
if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
switch_channel_t *channel = switch_core_session_get_channel(member->session);
switch_channel_event_set_data(channel, event);
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Conference-Name", conference->name);
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Member-ID", "%u", id);
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Action", "kick-member");
switch_event_fire(&event);
}
goto done;
} else {
stream->write_function(stream, "Non-Existant ID %u\n", id);
goto done;
}
} else {
stream->write_function(stream, "usage kick <id>\n");
goto done;
}
} else if (!strcasecmp(argv[1], "transfer")) {
char *transfer_usage = "Usage transfer <id> <confname>\n";
if (argc > 3) {
conference_member_t *member = NULL;
uint32_t id = atoi(argv[2]);
conference_obj_t *new_conference = NULL;
if ((new_conference = (conference_obj_t *) switch_core_hash_find(globals.conference_hash, argv[3]))) {
if ((member = conference_member_get(conference, id))) {
switch_channel_t *channel = switch_core_session_get_channel(member->session);
switch_event_t *event;
conference_del_member(member->last_conference, member);
conference_add_member(new_conference, member);
stream->write_function(stream, "OK Member %u sent to conference %s.\n", id, argv[3]);
if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
switch_channel_event_set_data(channel, event);
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Member-ID", "%u", member->id);
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Old-Conference-Name", conference->name);
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "New-Conference-Name", argv[3]);
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Action", "transfer");
switch_event_fire(&event);
}
goto done;
} else {
stream->write_function(stream, "No Member %u in conference %s.\n", id, conference->name);
goto done;
}
} else {
stream->write_function(stream, "No Conference called %s found.\n", argv[3]);
goto done;
}
} else {
stream->write_function(stream, transfer_usage);
goto done;
}
} else if (!strcasecmp(argv[1], "relate")) {
char *relate_usage = "Usage relate <id> <id> [nospeak|nohear|clear]\n";
if (argc > 4) {
uint8_t nospeak = 0, nohear = 0, clear = 0;
nospeak = strstr(argv[4], "nospeak") ? 1 : 0;
nohear = strstr(argv[4], "nohear") ? 1 : 0;
if (!strcasecmp(argv[4], "clear")) {
clear = 1;
}
if (!(clear || nospeak || nohear)) {
stream->write_function(stream, relate_usage);
goto done;
}
if (clear) {
conference_member_t *member = NULL;
uint32_t id = atoi(argv[2]);
uint32_t oid = atoi(argv[3]);
switch_mutex_lock(conference->mutex);
switch_mutex_lock(conference->member_mutex);
if ((member = conference_member_get(conference, id))) {
member_del_relationship(member, oid);
stream->write_function(stream, "relationship %u->%u cleared.", id, oid);
} else {
stream->write_function(stream, "relationship %u->%u not found", id, oid);
}
switch_mutex_unlock(conference->member_mutex);
switch_mutex_unlock(conference->mutex);
} else if (nospeak || nohear) {
conference_member_t *member = NULL, *other_member = NULL;
uint32_t id = atoi(argv[2]);
uint32_t oid = atoi(argv[3]);
switch_mutex_lock(conference->mutex);
switch_mutex_lock(conference->member_mutex);
if ((member = conference_member_get(conference, id)) && (other_member = conference_member_get(conference, oid))) {
conference_relationship_t *rel = NULL;
if ((rel = member_get_relationship(member, other_member))) {
rel->flags = 0;
} else {
rel = member_add_relationship(member, oid);
}
if (rel) {
switch_set_flag(rel, RFLAG_CAN_SPEAK | RFLAG_CAN_HEAR);
if (nospeak) {
switch_clear_flag(rel, RFLAG_CAN_SPEAK);
}
if (nohear) {
switch_clear_flag(rel, RFLAG_CAN_HEAR);
}
stream->write_function(stream, "ok %u->%u set\n", id, oid);
} else {
stream->write_function(stream, "error!\n");
}
} else {
stream->write_function(stream, "relationship %u->%u not found", id, oid);
}
switch_mutex_unlock(conference->member_mutex);
switch_mutex_unlock(conference->mutex);
}
} else {
stream->write_function(stream, relate_usage);
}
} else if (!strcasecmp(argv[1], "list")) {
char *d = ";";
if (argv[2]) {
if (argv[3] && !strcasecmp(argv[2], "delim")) {
d = argv[3];
if (*d == '"') {
if (++d) {
char *p;
if ((p = strchr(d, '"'))) {
*p = '\0';
}
} else {
d = ";";
}
}
}
}
conference_list(conference, stream, d);
} else {
stream->write_function(stream, "Command: %s not found.\n", argv[1]);
goto done;
}
} else {
stream->write_function(stream, "Command not specified.\n");
goto done;
}
} else {
stream->write_function(stream, topusage);
}
} else {
stream->write_function(stream, "Memory Error!\n");
}
done:
if (lbuf) {
free(lbuf);
}
return status;
}
static switch_status_t audio_bridge_on_ring(switch_core_session_t *session)
{
switch_channel_t *channel = NULL;
channel = switch_core_session_get_channel(session);
assert(channel != NULL);
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "CUSTOM RING\n");
/* put the channel in a passive state so we can loop audio to it */
return SWITCH_STATUS_FALSE;
}
static const switch_state_handler_table_t audio_bridge_peer_state_handlers = {
/*.on_init */ NULL,
/*.on_ring */ audio_bridge_on_ring,
/*.on_execute */ NULL,
/*.on_hangup */ NULL,
/*.on_loopback */ NULL,
/*.on_transmit */ NULL,
/*.on_hold */ NULL,
};
static switch_status_t conference_outcall(conference_obj_t *conference, switch_core_session_t *session, char *bridgeto, char *cid_name, char *cid_num)
{
switch_core_session_t *peer_session;
switch_caller_profile_t *caller_profile, *caller_caller_profile;
char *chan_type, *chan_data;
unsigned int timelimit = 60;
switch_channel_t *peer_channel;
time_t start;
switch_frame_t *read_frame = NULL;
switch_status_t status = SWITCH_STATUS_SUCCESS;
switch_channel_t *caller_channel = NULL;
chan_type = strdup(bridgeto);
if ((chan_data = strchr(chan_type, '/')) != 0) {
*chan_data = '\0';
chan_data++;
}
if (session) {
caller_channel = switch_core_session_get_channel(session);
assert(caller_channel != NULL);
caller_caller_profile = switch_channel_get_caller_profile(caller_channel);
if (!cid_name) {
cid_name = caller_caller_profile->caller_id_name;
}
if (!cid_num) {
cid_num = caller_caller_profile->caller_id_number;
}
caller_profile = switch_caller_profile_new(switch_core_session_get_pool(session),
caller_caller_profile->username,
caller_caller_profile->dialplan,
cid_name,
cid_num,
caller_caller_profile->network_addr,
NULL,
NULL,
caller_caller_profile->rdnis,
caller_caller_profile->source,
caller_caller_profile->context,
chan_data);
} else {
if (!cid_name) {
cid_name = conference->caller_id_name;
}
if (!cid_num) {
cid_num = conference->caller_id_number;
}
caller_profile = switch_caller_profile_new(conference->pool,
NULL,
NULL,
cid_name,
cid_num,
NULL,
NULL,
NULL,
NULL,
(char *) global_app_name,
NULL,
chan_data);
}
if (switch_core_session_outgoing_channel(session, chan_type, caller_profile, &peer_session, NULL) !=
SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Cannot Create Outgoing Channel!\n");
if (caller_channel) {
switch_channel_hangup(caller_channel, SWITCH_CAUSE_REQUESTED_CHAN_UNAVAIL);
}
status = SWITCH_STATUS_FALSE;
goto done;
}
peer_channel = switch_core_session_get_channel(peer_session);
assert(peer_channel != NULL);
switch_channel_add_state_handler(peer_channel, &audio_bridge_peer_state_handlers);
if (switch_core_session_runing(peer_session)) {
switch_channel_set_state(peer_channel, CS_RING);
} else {
switch_core_session_thread_launch(peer_session);
}
time(&start);
for (;;) {
int state = switch_channel_get_state(peer_channel);
if (state >= CS_RING) {
break;
}
if (caller_channel && !switch_channel_ready(caller_channel)) {
break;
}
if ((time(NULL) - start) > timelimit) {
break;
}
switch_yield(1000);
}
if (caller_channel) {
switch_channel_pre_answer(caller_channel);
}
while ((!caller_channel || switch_channel_ready(caller_channel)) &&
switch_channel_ready(peer_channel) &&
!switch_channel_test_flag(peer_channel, CF_ANSWERED) &&
!switch_channel_test_flag(peer_channel, CF_EARLY_MEDIA) &&
((time(NULL) - start) < timelimit)) {
/* read from the channel while we wait if the audio is up on it */
if (session && (switch_channel_test_flag(caller_channel, CF_ANSWERED) || switch_channel_test_flag(caller_channel, CF_EARLY_MEDIA))) {
switch_status_t status = switch_core_session_read_frame(session, &read_frame, 1000, 0);
if (!SWITCH_READ_ACCEPTABLE(status)) {
break;
}
if (read_frame) {
if (switch_core_session_write_frame(session, read_frame, 1000, 0) != SWITCH_STATUS_SUCCESS) {
break;
}
}
} else {
switch_yield(1000);
}
}
if (caller_channel && switch_channel_test_flag(peer_channel, CF_ANSWERED)) {
switch_channel_answer(caller_channel);
}
if (switch_channel_test_flag(peer_channel, CF_ANSWERED) || switch_channel_test_flag(peer_channel, CF_EARLY_MEDIA)) {
switch_caller_extension_t *extension = NULL;
if ((extension = switch_caller_extension_new(peer_session, caller_profile->destination_number,
caller_profile->destination_number)) == 0) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "memory error!\n");
status = SWITCH_STATUS_MEMERR;
goto done;
}
/* add them to the conference */
switch_caller_extension_add_application(peer_session, extension, (char *) global_app_name, conference->name);
switch_channel_set_caller_extension(peer_channel, extension);
switch_channel_set_state(peer_channel, CS_EXECUTE);
} else {
switch_channel_hangup(peer_channel, SWITCH_CAUSE_NO_ANSWER);
status = SWITCH_STATUS_FALSE;
goto done;
}
done:
free(chan_type);
return status;
}
/* Play a file */
static switch_status_t conference_local_play_file(switch_core_session_t *session, char *path, uint32_t leadin, char *buf, switch_size_t len)
{
uint32_t x = 0;
switch_status_t status = SWITCH_STATUS_SUCCESS;
for (x = 0; x < leadin; x++) {
switch_frame_t *read_frame;
switch_status_t status = switch_core_session_read_frame(session, &read_frame, 1000, 0);
if (!SWITCH_READ_ACCEPTABLE(status)) {
break;
}
}
if (status == SWITCH_STATUS_SUCCESS) {
status = switch_ivr_play_file(session, NULL, path, NULL, NULL, NULL, 0);
}
return status;
}
/* Application interface function that is called from the dialplan to join the channel to a conference */
static void conference_function(switch_core_session_t *session, char *data)
{
switch_codec_t *read_codec = NULL;
switch_memory_pool_t *pool = NULL, *freepool = NULL;
uint32_t flags = 0;
conference_member_t member = {0};
conference_obj_t *conference = NULL;
switch_channel_t *channel = NULL;
char *mydata = switch_core_session_strdup(session, data);
char *conf_name = NULL;
char *bridge_prefix = "bridge:";
char *bridgeto = NULL;
char *profile_name = NULL;
switch_xml_t cxml = NULL, cfg = NULL, profile = NULL, profiles = NULL;
channel = switch_core_session_get_channel(session);
assert(channel != NULL);
if (!mydata) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Pool Failure\n");
return;
}
/* Setup a memory pool to use. */
if (switch_core_new_memory_pool(&pool) != SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Pool Failure\n");
return;
}
if (!strncasecmp(mydata, bridge_prefix, strlen(bridge_prefix))) {
char *uuid = switch_core_session_get_uuid(session);
mydata += strlen(bridge_prefix);
conf_name = mydata;
if ((bridgeto = strchr(conf_name, ':'))) {
*bridgeto++ = '\0';
} else {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Config Error!\n");
goto done;
}
if ((profile_name = strchr(conf_name, '@'))) {
*profile_name++ = '\0';
/* Open the config from the xml registry */
if (!(cxml = switch_xml_open_cfg(global_cf_name, &cfg, NULL))) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "open of %s failed\n", global_cf_name);
goto done;
}
if ((profiles = switch_xml_child(cfg, "profiles"))) {
profile = switch_xml_find_child(profiles, "profile", "name", profile_name);
}
}
if (!strcmp(conf_name, "_uuid_")) {
conf_name = uuid;
}
if ((conference = (conference_obj_t *) switch_core_hash_find(globals.conference_hash, conf_name))) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Conference %s already exists!\n", conf_name);
goto done;
}
/* Create the conference object. */
conference = conference_new(conf_name, profile, pool);
/* Release the config registry handle */
switch_xml_free(cxml);
cxml = NULL;
if (!conference) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Memory Error!\n");
goto done;
}
/* Set the minimum number of members (once you go above it you cannot go below it) */
conference->min = 2;
/* Indicate the conference is dynamic */
switch_set_flag_locked(conference, CFLAG_DYNAMIC);
/* Start the conference thread for this conference */
launch_conference_thread(conference);
} else {
conf_name = mydata;
freepool = pool;
/* Figure out what conference to call. */
if (!(conference = (conference_obj_t *) switch_core_hash_find(globals.conference_hash, conf_name))) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "No Conference called %s found.\n", conf_name);
goto done;
}
if (conference->pin) {
char term = '\0';
char pin[80] = "";
char *buf;
/* Answer the channel */
switch_channel_answer(channel);
if (conference->pin_sound) {
conference_local_play_file(session, conference->pin_sound, 20, pin, sizeof(pin));
}
if (strlen(pin) < strlen(conference->pin)) {
buf = pin + strlen(pin);
switch_ivr_collect_digits_count(session, buf, sizeof(pin) - (unsigned int)strlen(pin), (unsigned int)strlen(conference->pin) - (unsigned int)strlen(pin), "#", &term, 10000);
}
if (strcmp(pin, conference->pin)) {
if (conference->bad_pin_sound) {
conference_local_play_file(session, conference->bad_pin_sound, 20, NULL, 0);
}
goto done;
}
}
if (switch_test_flag(conference, CFLAG_LOCKED)) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_NOTICE, "Conference %s is locked.\n", conf_name);
if (conference->locked_sound) {
/* Answer the channel */
switch_channel_answer(channel);
conference_local_play_file(session, conference->locked_sound, 20, NULL, 0);
}
goto done;
}
}
if (bridgeto) {
if (conference_outcall(conference, session, bridgeto, NULL, NULL) != SWITCH_STATUS_SUCCESS) {
goto done;
}
}
/* Save the original read codec. */
read_codec = switch_core_session_get_read_codec(session);
/* Setup a Signed Linear codec for reading audio. */
if (switch_core_codec_init(&member.read_codec,
"L16",
read_codec->implementation->samples_per_second,
conference->interval,
1,
SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE,
NULL,
pool) == SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Raw Codec Activation Success L16@%uhz 1 channel %dms\n",
conference->rate, conference->interval);
} else {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Raw Codec Activation Failed L16@%uhz 1 channel %dms\n",
conference->rate, conference->interval);
flags = 0;
goto done;
}
/* Setup a Signed Linear codec for writing audio. */
if (switch_core_codec_init(&member.write_codec,
"L16",
read_codec->implementation->samples_per_second,
conference->interval,
1,
SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE,
NULL,
pool) == SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Raw Codec Activation Success L16@%uhz 1 channel %dms\n",
conference->rate, conference->interval);
} else {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Raw Codec Activation Failed L16@%uhz 1 channel %dms\n",
conference->rate, conference->interval);
flags = 0;
goto codec_done2;
}
/* Setup an audio buffer for the incoming audio */
if (switch_buffer_create(pool, &member.audio_buffer, CONF_BUFFER_SIZE) != SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Memory Error Creating Audio Buffer!\n");
goto codec_done1;
}
/* Setup an audio buffer for the outgoing audio */
if (switch_buffer_create(pool, &member.mux_buffer, CONF_BUFFER_SIZE) != SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Memory Error Creating Audio Buffer!\n");
goto codec_done1;
}
/* Prepare MUTEXS */
member.id = next_member_id();
member.pool = pool;
member.session = session;
switch_mutex_init(&member.flag_mutex, SWITCH_MUTEX_NESTED, pool);
switch_mutex_init(&member.audio_in_mutex, SWITCH_MUTEX_NESTED, pool);
switch_mutex_init(&member.audio_out_mutex, SWITCH_MUTEX_NESTED, pool);
/* Install our Signed Linear codec so we get the audio in that format */
switch_core_session_set_read_codec(member.session, &member.read_codec);
/* Add the caller to the conference */
if (conference_add_member(conference, &member) != SWITCH_STATUS_SUCCESS) {
goto codec_done1;
}
switch_set_flag_locked((&member), MFLAG_RUNNING | MFLAG_CAN_SPEAK | MFLAG_CAN_HEAR);
/* Run the confernece loop */
conference_loop(&member);
/* Remove the caller from the conference */
conference_del_member(member.last_conference, &member);
/* Put the original codec back */
switch_core_session_set_read_codec(member.session, read_codec);
/* Clean Up. codec_done(X): is for error situations after the codecs were setup and done: is for situations before */
codec_done1:
switch_core_codec_destroy(&member.read_codec);
codec_done2:
switch_core_codec_destroy(&member.write_codec);
done:
/* Release the config registry handle */
if (cxml) {
switch_xml_free(cxml);
}
if (freepool) {
switch_core_destroy_memory_pool(&freepool);
}
if (switch_test_flag(&member, MFLAG_KICKED) && conference->kicked_sound) {
switch_ivr_play_file(session, NULL, conference->kicked_sound, NULL, NULL, NULL, 0);
}
switch_core_session_reset(session);
}
/* Create a thread for the conference and launch it */
static void launch_conference_thread(conference_obj_t *conference)
{
switch_thread_t *thread;
switch_threadattr_t *thd_attr = NULL;
switch_set_flag_locked(conference, CFLAG_RUNNING);
switch_threadattr_create(&thd_attr, conference->pool);
switch_threadattr_detach_set(thd_attr, 1);
switch_threadattr_stacksize_set(thd_attr, SWITCH_THREAD_STACKSIZE);
switch_mutex_lock(globals.hash_mutex);
switch_core_hash_insert(globals.conference_hash, conference->name, conference);
switch_mutex_unlock(globals.hash_mutex);
switch_thread_create(&thread, thd_attr, conference_thread_run, conference, conference->pool);
}
static void *SWITCH_THREAD_FUNC input_thread_run(switch_thread_t *thread, void *obj)
{
conference_member_t *member = obj;
switch_channel_t *channel;
switch_status_t status;
switch_frame_t *read_frame = NULL;
switch_codec_t *read_codec;
uint32_t hangover = 40,
hangunder = 15,
hangover_hits = 0,
hangunder_hits = 0,
energy_level = 0,
diff_level = 400;
uint8_t talking = 0;
assert(member != NULL);
energy_level = member->conference->energy_level;
channel = switch_core_session_get_channel(member->session);
assert(channel != NULL);
read_codec = switch_core_session_get_read_codec(member->session);
assert(read_codec != NULL);
/* As long as we have a valid read, feed that data into an input buffer where the conference thread will take it
and mux it with any audio from other channels. */
while(switch_test_flag(member, MFLAG_RUNNING) && switch_channel_ready(channel)) {
/* Read a frame. */
status = switch_core_session_read_frame(member->session, &read_frame, -1, 0);
/* end the loop, if appropriate */
if (!SWITCH_READ_ACCEPTABLE(status) || !switch_test_flag(member, MFLAG_RUNNING)) {
break;
}
if (switch_test_flag(read_frame, SFF_CNG)) {
continue;
}
if (switch_test_flag(member, MFLAG_CAN_SPEAK) && energy_level) {
uint32_t energy = 0, i = 0, samples = 0, j = 0, score = 0;
int16_t *data;
data = read_frame->data;
samples = read_frame->datalen / sizeof(*data);
for (i = 0; i < samples; i++) {
energy += abs(data[j]);
j += read_codec->implementation->number_of_channels;
}
score = energy / samples;
if (score > energy_level) {
uint32_t diff = score - energy_level;
if (hangover_hits) {
hangover_hits--;
}
if (diff >= diff_level || ++hangunder_hits >= hangunder) {
hangover_hits = hangunder_hits = 0;
if (!talking) {
switch_event_t *event;
talking = 1;
if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
switch_channel_event_set_data(channel, event);
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Conference-Name", member->conference->name);
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Member-ID", "%u", member->id);
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Action", "start-talking");
switch_event_fire(&event);
}
}
}
} else {
if (hangunder_hits) {
hangunder_hits--;
}
if (talking) {
switch_event_t *event;
if (++hangover_hits >= hangover) {
hangover_hits = hangunder_hits = 0;
talking = 0;
if (switch_event_create_subclass(&event, SWITCH_EVENT_CUSTOM, CONF_EVENT_MAINT) == SWITCH_STATUS_SUCCESS) {
switch_channel_event_set_data(channel, event);
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Conference-Name", member->conference->name);
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Member-ID", "%u", member->id);
switch_event_add_header(event, SWITCH_STACK_BOTTOM, "Action", "stop-talking");
switch_event_fire(&event);
}
}
}
}
}
/* skip frames that are not actual media or when we are muted or silent */
if (talking && switch_test_flag(member, MFLAG_CAN_SPEAK)) {
/* Write the audio into the input buffer */
switch_mutex_lock(member->audio_in_mutex);
switch_buffer_write(member->audio_buffer, read_frame->data, read_frame->datalen);
switch_mutex_unlock(member->audio_in_mutex);
}
}
switch_clear_flag_locked(member, MFLAG_ITHREAD);
return NULL;
}
/* Create a thread for the conference and launch it */
static void launch_input_thread(conference_member_t *member, switch_memory_pool_t *pool)
{
switch_thread_t *thread;
switch_threadattr_t *thd_attr = NULL;
switch_threadattr_create(&thd_attr, pool);
switch_threadattr_detach_set(thd_attr, 1);
switch_threadattr_stacksize_set(thd_attr, SWITCH_THREAD_STACKSIZE);
switch_set_flag_locked(member, MFLAG_ITHREAD);
switch_thread_create(&thread, thd_attr, input_thread_run, member, pool);
}
static const switch_application_interface_t conference_application_interface = {
/*.interface_name */ global_app_name,
/*.application_function */ conference_function,
NULL, NULL, NULL,
/*.next*/ NULL
};
static switch_api_interface_t conf_api_interface = {
/*.interface_name */ "conference",
/*.desc */ "Conference",
/*.function */ conf_function
/*.next */
};
static switch_loadable_module_interface_t conference_module_interface = {
/*.module_name */ modname,
/*.endpoint_interface */ NULL,
/*.timer_interface */ NULL,
/*.dialplan_interface */ NULL,
/*.codec_interface */ NULL,
/*.application_interface */ &conference_application_interface,
/*.api_interface */ &conf_api_interface,
/*.file_interface */ NULL,
/*.speech_interface */ NULL,
/*.directory_interface */ NULL
};
/* create a new conferene with a specific profile */
static conference_obj_t *conference_new(char *name, switch_xml_t profile, switch_memory_pool_t *pool)
{
conference_obj_t *conference;
switch_xml_t param;
char *rate_name = NULL;
char *interval_name = NULL;
char *timer_name = NULL;
char *tts_engine = NULL;
char *tts_voice = NULL;
char *enter_sound = NULL;
char *exit_sound = NULL;
char *alone_sound = NULL;
char *ack_sound = NULL;
char *nack_sound = NULL;
char *muted_sound = NULL;
char *unmuted_sound = NULL;
char *locked_sound = NULL;
char *kicked_sound = NULL;
char *pin = NULL;
char *pin_sound = NULL;
char *bad_pin_sound = NULL;
char *energy_level = NULL;
char *caller_id_name = NULL;
char *caller_id_number = NULL;
uint32_t rate = 8000, interval = 20;
switch_status_t status;
/* Conference Name */
if (switch_strlen_zero(name)) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Invalid Record! no name.\n");
return NULL;
}
for (param = switch_xml_child(profile, "param"); param; param = param->next) {
char *var = (char *) switch_xml_attr_soft(param, "name");
char *val = (char *) switch_xml_attr_soft(param, "value");
if (!strcasecmp(var, "rate")) {
rate_name = val;
} else if (!strcasecmp(var, "interval")) {
interval_name= val;
} else if (!strcasecmp(var, "timer_name")) {
timer_name= val;
} else if (!strcasecmp(var, "tts_engine")) {
tts_engine= val;
} else if (!strcasecmp(var, "tts_voice")) {
tts_voice= val;
} else if (!strcasecmp(var, "enter_sound")) {
enter_sound = val;
} else if (!strcasecmp(var, "exit_sound")) {
exit_sound = val;
} else if (!strcasecmp(var, "alone_sound")) {
alone_sound = val;
} else if (!strcasecmp(var, "ack_sound")) {
ack_sound = val;
} else if (!strcasecmp(var, "nack_sound")) {
nack_sound = val;
} else if (!strcasecmp(var, "muted_sound")) {
muted_sound = val;
} else if (!strcasecmp(var, "unmuted_sound")) {
unmuted_sound = val;
} else if (!strcasecmp(var, "locked_sound")) {
locked_sound= val;
} else if (!strcasecmp(var, "kicked_sound")) {
kicked_sound = val;
} else if (!strcasecmp(var, "pin")) {
pin = val;
} else if (!strcasecmp(var, "pin_sound")) {
pin_sound = val;
} else if (!strcasecmp(var, "bad_pin_sound")) {
bad_pin_sound = val;
} else if (!strcasecmp(var, "energy_level")) {
energy_level = val;
} else if (!strcasecmp(var, "caller_id_name")) {
caller_id_name = val;
} else if (!strcasecmp(var, "caller_id_number")) {
caller_id_number = val;
}
}
/* Set defaults and various paramaters */
/* Speed in hertz */
if (!switch_strlen_zero(rate_name)) {
uint32_t r = atoi(rate_name);
if (r) {
rate = r;
}
}
/* Packet Interval in milliseconds */
if (!switch_strlen_zero(interval_name)) {
uint32_t i = atoi(interval_name);
if (i) {
interval = i;
}
}
/* Timer module to use */
if (switch_strlen_zero(timer_name)) {
timer_name = "soft";
}
/* TTS engine to use */
if (switch_strlen_zero(tts_engine)) {
tts_engine = "cepstral";
}
/* TTS voice to use */
if (switch_strlen_zero(tts_voice)) {
tts_voice = "david";
}
/* Caller ID Name */
if (switch_strlen_zero(caller_id_name)) {
caller_id_name = (char *) global_app_name;
}
/* Caller ID Number */
if (switch_strlen_zero(caller_id_number)) {
caller_id_number = "0000000000";
}
if (!pool) {
/* Setup a memory pool to use. */
if (switch_core_new_memory_pool(&pool) != SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Pool Failure\n");
status = SWITCH_STATUS_TERM;
return NULL;
}
}
/* Create the conference object. */
if (!(conference = switch_core_alloc(pool, sizeof(*conference)))) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "Memory Error!\n");
status = SWITCH_STATUS_TERM;
return NULL;
}
/* Initilize the object with some settings */
conference->pool = pool;
conference->timer_name = switch_core_strdup(conference->pool, timer_name);
conference->tts_engine = switch_core_strdup(conference->pool, tts_engine);
conference->tts_voice = switch_core_strdup(conference->pool, tts_voice);
conference->caller_id_name = switch_core_strdup(conference->pool, caller_id_name);
conference->caller_id_number = switch_core_strdup(conference->pool, caller_id_number);
if (!switch_strlen_zero(enter_sound)) {
conference->enter_sound = switch_core_strdup(conference->pool, enter_sound);
}
if (!switch_strlen_zero(exit_sound)) {
conference->exit_sound = switch_core_strdup(conference->pool, exit_sound);
}
if (!switch_strlen_zero(ack_sound)) {
conference->ack_sound = switch_core_strdup(conference->pool, ack_sound);
}
if (!switch_strlen_zero(nack_sound)) {
conference->nack_sound = switch_core_strdup(conference->pool, nack_sound);
}
if (!switch_strlen_zero(muted_sound)) {
conference->muted_sound = switch_core_strdup(conference->pool, muted_sound);
}
if (!switch_strlen_zero(unmuted_sound)) {
conference->unmuted_sound = switch_core_strdup(conference->pool, unmuted_sound);
}
if (!switch_strlen_zero(kicked_sound)) {
conference->kicked_sound = switch_core_strdup(conference->pool, kicked_sound);
}
if (!switch_strlen_zero(pin_sound)) {
conference->pin_sound = switch_core_strdup(conference->pool, pin_sound);
}
if (!switch_strlen_zero(bad_pin_sound)) {
conference->bad_pin_sound = switch_core_strdup(conference->pool, bad_pin_sound);
}
if (!switch_strlen_zero(pin)) {
conference->pin = switch_core_strdup(conference->pool, pin);
}
if (!switch_strlen_zero(alone_sound)) {
conference->alone_sound = switch_core_strdup(conference->pool, alone_sound);
}
if (!switch_strlen_zero(locked_sound)) {
conference->locked_sound = switch_core_strdup(conference->pool, locked_sound);
}
if (!switch_strlen_zero(energy_level)) {
conference->energy_level = atoi(energy_level);
}
conference->name = switch_core_strdup(conference->pool, name);
conference->rate = rate;
conference->interval = interval;
/* Activate the conference mutex for exclusivity */
switch_mutex_init(&conference->mutex, SWITCH_MUTEX_NESTED, conference->pool);
switch_mutex_init(&conference->member_mutex, SWITCH_MUTEX_NESTED, conference->pool);
switch_mutex_init(&conference->flag_mutex, SWITCH_MUTEX_NESTED, conference->pool);
switch_thread_rwlock_create(&conference->rwlock, conference->pool);
return conference;
}
/* Called by FreeSWITCH when the module loads */
SWITCH_MOD_DECLARE(switch_status_t) switch_module_load(const switch_loadable_module_interface_t **module_interface, char *filename)
{
switch_xml_t cfg, cxml, room, rooms, profiles, profile = NULL;
conference_obj_t *conference;
switch_status_t status = SWITCH_STATUS_SUCCESS;
memset(&globals, 0, sizeof(globals));
/* Connect my internal structure to the blank pointer passed to me */
*module_interface = &conference_module_interface;
if (switch_event_reserve_subclass(CONF_EVENT_MAINT) != SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Couldn't register subclass %s!", CONF_EVENT_MAINT);
return SWITCH_STATUS_TERM;
}
/* Setup the pool */
if (switch_core_new_memory_pool(&globals.conference_pool) != SWITCH_STATUS_SUCCESS) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_CRIT, "OH OH no conference pool\n");
return SWITCH_STATUS_TERM;
}
/* Open the config from the xml registry */
if (!(cxml = switch_xml_open_cfg(global_cf_name, &cfg, NULL))) {
switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "open of %s failed\n", global_cf_name);
return SWITCH_STATUS_TERM;
}
/* Setup a hash to store conferences by name */
switch_core_hash_init(&globals.conference_hash, globals.conference_pool);
switch_mutex_init(&globals.conference_mutex, SWITCH_MUTEX_NESTED, globals.conference_pool);
switch_mutex_init(&globals.id_mutex, SWITCH_MUTEX_NESTED, globals.conference_pool);
switch_mutex_init(&globals.hash_mutex, SWITCH_MUTEX_NESTED, globals.conference_pool);
/* Itterate the config */
if ((rooms = switch_xml_child(cfg, "rooms"))) {
/* Parse various rooms. */
for (room = switch_xml_child(rooms, "room"); room; room = room->next) {
char *name = (char *) switch_xml_attr_soft(room, "name");
char *pname = (char *) switch_xml_attr_soft(room, "profile");
if ((profiles = switch_xml_child(cfg, "profiles"))) {
profile = switch_xml_find_child(profiles, "profile", "name", pname);
}
if ((conference = conference_new(name, profile, NULL))) {
/* Start the conference thread for this conference */
launch_conference_thread(conference);
}
}
}
/* Release the config registry handle */
switch_xml_free(cxml);
/* indicate that the module should continue to be loaded */
return status;
}
<?xml version="1.0" encoding="Windows-1252"?>
<VisualStudioProject
ProjectType="Visual C++"
Version="8.00"
Name="mod_conference"
ProjectGUID="{C24FB505-05D7-4319-8485-7540B44C8603}"
RootNamespace="mod_conference"
Keyword="Win32Proj"
>
<Platforms>
<Platform
Name="Win32"
/>
</Platforms>
<ToolFiles>
</ToolFiles>
<Configurations>
<Configuration
Name="Debug|Win32"
OutputDirectory="Debug"
IntermediateDirectory="Debug"
ConfigurationType="2"
InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops"
CharacterSet="2"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
/>
<Tool
Name="VCCLCompilerTool"
Optimization="0"
AdditionalIncludeDirectories="&quot;$(InputDir)..\..\..\include&quot;;&quot;$(InputDir)include&quot;;&quot;$(InputDir)..\..\..\..\libs\include&quot;"
PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS;_USRDLL;MOD_EXPORTS"
MinimalRebuild="true"
BasicRuntimeChecks="3"
RuntimeLibrary="1"
UsePrecompiledHeader="0"
WarningLevel="4"
WarnAsError="true"
Detect64BitPortabilityProblems="true"
DebugInformationFormat="4"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
OutputFile="..\..\..\..\w32\vsnet\$(OutDir)/mod/mod_conference.dll"
LinkIncremental="2"
AdditionalLibraryDirectories="..\..\..\..\w32\vsnet\$(OutDir)"
GenerateDebugInformation="true"
ProgramDatabaseFile="$(OutDir)/mod_conference.pdb"
SubSystem="2"
ImportLibrary="$(OutDir)/mod_conference.lib"
TargetMachine="1"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCWebDeploymentTool"
/>
<Tool
Name="VCPostBuildEventTool"
/>
</Configuration>
<Configuration
Name="Release|Win32"
OutputDirectory="Release"
IntermediateDirectory="Release"
ConfigurationType="2"
InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops"
CharacterSet="2"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
/>
<Tool
Name="VCCLCompilerTool"
AdditionalIncludeDirectories="&quot;$(InputDir)..\..\..\include&quot;;&quot;$(InputDir)include&quot;;&quot;$(InputDir)..\..\..\..\libs\include&quot;"
PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS;_USRDLL;MOD_EXPORTS"
RuntimeLibrary="0"
UsePrecompiledHeader="0"
WarningLevel="4"
WarnAsError="true"
Detect64BitPortabilityProblems="true"
DebugInformationFormat="3"
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
OutputFile="..\..\..\..\w32\vsnet\$(OutDir)/mod/mod_conference.dll"
LinkIncremental="1"
AdditionalLibraryDirectories="..\..\..\..\w32\vsnet\$(OutDir)"
GenerateDebugInformation="true"
SubSystem="2"
OptimizeReferences="2"
EnableCOMDATFolding="2"
ImportLibrary="$(OutDir)/mod_conference.lib"
TargetMachine="1"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCWebDeploymentTool"
/>
<Tool
Name="VCPostBuildEventTool"
/>
</Configuration>
</Configurations>
<References>
</References>
<Files>
<Filter
Name="Source Files"
Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx"
UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"
>
<File
RelativePath=".\mod_conference.c"
>
</File>
</Filter>
<Filter
Name="Header Files"
Filter="h;hpp;hxx;hm;inl;inc;xsd"
UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"
>
</Filter>
<Filter
Name="Resource Files"
Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx"
UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"
>
</Filter>
</Files>
<Globals>
</Globals>
</VisualStudioProject>
......@@ -208,6 +208,11 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mod_rss", "..\..\src\mod\ap
{202D7A4E-760D-4D0E-AFA1-D7459CED30FF} = {202D7A4E-760D-4D0E-AFA1-D7459CED30FF}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mod_conference", "..\..\src\mod\applications\mod_conference\mod_conference.vcproj", "{C24FB505-05D7-4319-8485-7540B44C8603}"
ProjectSection(ProjectDependencies) = postProject
{202D7A4E-760D-4D0E-AFA1-D7459CED30FF} = {202D7A4E-760D-4D0E-AFA1-D7459CED30FF}
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Win32 = Debug|Win32
......@@ -354,6 +359,10 @@ Global
{B69247FA-ECD6-40ED-8E44-5CA6C3BAF9A4}.Debug|Win32.Build.0 = Debug|Win32
{B69247FA-ECD6-40ED-8E44-5CA6C3BAF9A4}.Release|Win32.ActiveCfg = Release|Win32
{B69247FA-ECD6-40ED-8E44-5CA6C3BAF9A4}.Release|Win32.Build.0 = Release|Win32
{C24FB505-05D7-4319-8485-7540B44C8603}.Debug|Win32.ActiveCfg = Debug|Win32
{C24FB505-05D7-4319-8485-7540B44C8603}.Debug|Win32.Build.0 = Debug|Win32
{C24FB505-05D7-4319-8485-7540B44C8603}.Release|Win32.ActiveCfg = Release|Win32
{C24FB505-05D7-4319-8485-7540B44C8603}.Release|Win32.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
......@@ -376,6 +385,7 @@ Global
{0E2C6395-13B9-46E5-9264-8859D346018D} = {E72B5BCB-6462-4D23-B419-3AF1A4AC3D78}
{30A5B29C-983E-4580-9FD0-D647CCDCC7EB} = {E72B5BCB-6462-4D23-B419-3AF1A4AC3D78}
{B69247FA-ECD6-40ED-8E44-5CA6C3BAF9A4} = {E72B5BCB-6462-4D23-B419-3AF1A4AC3D78}
{C24FB505-05D7-4319-8485-7540B44C8603} = {E72B5BCB-6462-4D23-B419-3AF1A4AC3D78}
{3A5B9131-F20C-4A85-9447-6C1610941CEE} = {9460B5F1-0A95-41C4-BEB7-9C2C96459A7C}
{5FD31A25-5D83-4794-8BEE-904DAD84CE71} = {9460B5F1-0A95-41C4-BEB7-9C2C96459A7C}
{FE3540C5-3303-46E0-A69E-D92F775687F1} = {9460B5F1-0A95-41C4-BEB7-9C2C96459A7C}
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论