From a6e3ccb8909e4b3fb8d9a1decd501c1e531b0b83 Mon Sep 17 00:00:00 2001 From: Jeffrey C. Ollie Date: Sun, 18 Nov 2007 22:23:17 -0600 Subject: [PATCH] Add app_conference rev 911 --- apps/Makefile | 14 + apps/app_conference.c | 113 ++ apps/conference/CLI.txt | 96 + apps/conference/Flags.txt | 31 + apps/conference/LICENSE | 341 ++++ apps/conference/README | 125 ++ apps/conference/README.videoswitch | 87 + apps/conference/TODO | 4 + apps/conference/app_conference.h | 244 +++ apps/conference/cli.c | 1265 ++++++++++++++ apps/conference/cli.h | 99 ++ apps/conference/common.h | 63 + apps/conference/conf_frame.h | 73 + apps/conference/conference.c | 2923 +++++++++++++++++++++++++++++++ apps/conference/conference.h | 194 +++ apps/conference/frame.c | 679 ++++++++ apps/conference/frame.h | 75 + apps/conference/member.c | 3332 ++++++++++++++++++++++++++++++++++++ apps/conference/member.h | 315 ++++ 19 files changed, 10073 insertions(+), 0 deletions(-) create mode 100644 apps/app_conference.c create mode 100644 apps/conference/CLI.txt create mode 100644 apps/conference/Flags.txt create mode 100644 apps/conference/LICENSE create mode 100644 apps/conference/README create mode 100644 apps/conference/README.videoswitch create mode 100644 apps/conference/TODO create mode 100644 apps/conference/app_conference.h create mode 100644 apps/conference/cli.c create mode 100644 apps/conference/cli.h create mode 100644 apps/conference/common.h create mode 100644 apps/conference/conf_frame.h create mode 100644 apps/conference/conference.c create mode 100644 apps/conference/conference.h create mode 100644 apps/conference/frame.c create mode 100644 apps/conference/frame.h create mode 100644 apps/conference/member.c create mode 100644 apps/conference/member.h diff --git a/apps/Makefile b/apps/Makefile index d0e9215..0722e82 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -38,4 +38,18 @@ endif all: _all +app_conference.o: ASTCFLAGS+=-DSILDET=2 -Iconference + +conference/conference.o: ASTCFLAGS+=-DSILDET=2 -Iconference + +conference/member.o: ASTCFLAGS+=-DSILDET=2 -Iconference + +conference/frame.o: ASTCFLAGS+=-DSILDET=2 -Iconference + +conference/cli.o: ASTCFLAGS+=-DSILDET=2 -Iconference + +app_conference.so: app_conference.o conference/conference.o conference/member.o conference/frame.o conference/cli.o + $(ECHO_PREFIX) echo " [LD] $^ -> $@" + $(CMD_PREFIX) $(CXX) $(PTHREAD_CFLAGS) $(ASTLDFLAGS) $(SOLINK) -o $@ $^ -lspeex + include $(ASTTOPDIR)/Makefile.moddir_rules diff --git a/apps/app_conference.c b/apps/app_conference.c new file mode 100644 index 0000000..824d5dd --- /dev/null +++ b/apps/app_conference.c @@ -0,0 +1,113 @@ +/* + * app_conference + * + * A channel independent conference application for Asterisk + * + * Copyright (C) 2002, 2003 Junghanns.NET GmbH + * Copyright (C) 2003, 2004 HorizonLive.com, Inc. + * Copyright (C) 2005, 2006 HorizonWimba, Inc. + * Copyright (C) 2007 Wimba, Inc. + * + * Klaus-Peter Junghanns + * + * Video Conferencing support added by + * Neil Stratford + * Copyright (C) 2005, 2005 Vipadia Limited + * + * VAD driven video conferencing, text message support + * and miscellaneous enhancements added by + * Mihai Balea + * + * This program may be modified and distributed under the + * terms of the GNU General Public License. You should have received + * a copy of the GNU General Public License along with this + * program; if not, write to the Free Software Foundation, Inc. + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "asterisk.h" + +// SVN revision number, provided by make +#ifndef REVISION +#define REVISION "unknown" +#endif + +static char *revision = REVISION; + +ASTERISK_FILE_VERSION(__FILE__, REVISION) + +#include "app_conference.h" +#include "common.h" + +/* + * a conference has n + 1 threads, where n is the number of + * members and 1 is a conference thread which sends audio + * back to the members. + * + * each member thread reads frames from the channel and + * add's them to the member's frame queue. + * + * the conference thread reads frames from each speaking members + * queue, mixes them, and then re-queues them for the member thread + * to send back to the user. + */ + +static char *app = "Conference"; +static char *synopsis = "Channel Independent Conference"; +static char *descrip = "Channel Independent Conference Application"; + +static int app_conference_main(struct ast_channel* chan, void* data) +{ + int res ; + struct ast_module_user *u ; + + u = ast_module_user_add(chan); + + // call member thread function + res = member_exec( chan, data ) ; + + ast_module_user_remove(u); + + return res ; +} + +static int unload_module( void ) +{ + ast_log( LOG_NOTICE, "unloading app_conference module\n" ) ; + + ast_module_user_hangup_all(); + + unregister_conference_cli() ; + + return ast_unregister_application( app ) ; +} + +static int load_module( void ) +{ + ast_log( LOG_NOTICE, "Loading app_conference module, revision=%s\n", revision) ; + + init_conference() ; + + register_conference_cli() ; + + return ast_register_application( app, app_conference_main, synopsis, descrip ) ; +} + +// increment a timeval by ms milliseconds +void add_milliseconds(struct timeval* tv, long ms) +{ + // add the microseconds to the microseconds field + tv->tv_usec += ( ms * 1000 ) ; + + // calculate the number of seconds to increment + long s = ( tv->tv_usec / 1000000 ) ; + + // adjust the microsends field + if ( s > 0 ) tv->tv_usec -= ( s * 1000000 ) ; + + // increment the seconds field + tv->tv_sec += s ; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, + "Channel Independent Conference Application"); diff --git a/apps/conference/CLI.txt b/apps/conference/CLI.txt new file mode 100644 index 0000000..48acf5a --- /dev/null +++ b/apps/conference/CLI.txt @@ -0,0 +1,96 @@ +Current command line used by app_conference +Please note that app_conference is still work in progress, so this document might be outdated. As always, the source code is the definitive reference (cli.[ch] and conference.[ch]). You can also obtain help/usage information by using Asterisk CLI help system ("help conference") + +A member in a conference can be referred to by its id or by its channel. Id is a positive number assigned automatically when the member joins the conference. Channel is Asterisk channel identifier. To obtain a list of member ids and channels in a conference, do: + +*CLI> conference list + + +- conference debug: enable debugging for a conference + usage: conference debug [ on | off ] + +- conference end: stops a conference + usage: conference end + +- conference kick: kick member from a conference + usage: conference kick + +- conference kickchannel: kick channel from a conference + usage: conference kickchannel + +- conference list: list members of a conference. If no conference is specified, all conferences are listed + usage: conference list {conference_name} + +- conference lock: locks incoming video to a member + usage: conference lock + +- conference lockchannel: locks incoming video to a channel + usage: conference lockchannel + +- conference mute: mute member in a conference + usage: conference mute + +- conference mutechannel: mute channel in a conference + usage: conference mutechannel + +- conference play sound: play a sound to a conference member + usage: conference play sound [mute] + If mute is specified, all other audio is muted while the sound is played back. + +- conference restart: kick all users in all conferences + usage: conference restart + +- conference set default: sets default video source + usage: conference set default + use a negative value for member if you want to clear the default + +- conference set defaultchannel: sets default video source channel + usage: conference set defaultchannel + +- conference show stats: show conference stats + usage: conference show stats + +- conference text: sends a text message to a member. Depends on the member's channel capabilities. + usage: conference text + +- conference textbroadcast: sends a text message to all members in a conference + usage: conference textbroadcast + +- conference textchannel: sends a text message to a channel + usage: conference textchannel + +- conference unlock: unlocks incoming video + usage: conference unlock + +- conference unmute: unmute member in a conference + usage: conference unmute + +- conference unmutechannel: unmute channel in a conference + usage: conference unmutechannel + +- conference video mute: mutes video from a member + usage: conference video mute + +- conference video mutechannel: mutes video from a channel + usage: conference video mutechannel + +- conference video unmute: unmutes video from a member + usage: conference video unmute + +- conference video unmutechannel: unmutes video from a channel + usage: conference video unmutechannel + +- conference viewchannel: switch video for a channel in a conference + usage: conference viewchannel + +- conference viewstream: switch video for a member a conference + usage: conference viewstream + +- conference drive: drive VAD video switching of destination member using audio from source member + usage: conference drive [destination member] + If destination member is missing or negative, break existing connection + +- conference drivechannel: drive VAD video switching of destination channel using audio from source channel + usage: conference drivechannel [destination channel] + If destination channel is missing, break existing connection + diff --git a/apps/conference/Flags.txt b/apps/conference/Flags.txt new file mode 100644 index 0000000..dbb9732 --- /dev/null +++ b/apps/conference/Flags.txt @@ -0,0 +1,31 @@ +Current dialplan flags used by app_conference +Please note that app_conference is still work in progress, so this document might be outdated. As always, the source code is the definitive reference (member.c in create_member()) + +Mute/no receive options: +'C' : member starts with video muted +'c' : member starts unable to receive video +'L' : member starts with audio muted +'l' : member starts unable to receive audio + +Speex preprocessing options (right now app_conference does preprocessing only for Zaptel members): +'V' : enable speex preprocessing Voice Activity Detection +'D' : enable speex preprocessing De-noise +'A' : enable speex preprocessing Automatic Gain Control +'T' : member connects through Zaptel, so speex preprocessing should be enabled + +DTMF options: +'X' : enable DTMF switch: video can be switched by users using DTMF. Do not use with 'S'. +'R' : enable DTMF relay: DTMF tones generate a manager event +If neither 'X' nor 'R' are present, DTMF tones will be forwarded to all members in the conference + +Moderator/video switch options: +'M' : member is a "moderator". When a moderator quits, all members are kicked and the conference is disabled. +'S' : member accepts VAD controlled video switching. Do not use with 'X'. + +Miscellaneous: +'t' : member accepts text based control messages. The messages are described in a separate document +'N' : Assume that the member starts off with camera disabled. + +Future development (these are not implemented yet): +'x' : marked member. We plan to change the behavior so that when ALL moderators quit, all members that are marked will get kicked. Other members in the conference are not affected. + diff --git a/apps/conference/LICENSE b/apps/conference/LICENSE new file mode 100644 index 0000000..a52b16e --- /dev/null +++ b/apps/conference/LICENSE @@ -0,0 +1,341 @@ + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/apps/conference/README b/apps/conference/README new file mode 100644 index 0000000..4c103ce --- /dev/null +++ b/apps/conference/README @@ -0,0 +1,125 @@ +Introduction + +App_conference is a channel-independent conference application. +It features efficient audio mixing algorithms as well as video selection +support based on VAD, DTMF or CLI. + + +Design goals + +Appconference has several design goals which are different than Meetme: + + * It does not require a zap channel for timing. + * It is very efficient when used with channels which support DTX (silence + detection/discontinuous transmission). + + * It can do VAD on channels which do not support DTX (although this + is more expensive than just mixing them, but less expensive then + encoding; therefore it might still be a win). + * It presents messages on the Monitor interface for determine which + speakers are active. + +Mixing design + + * Minimize encoding/decoding, minimize mixing. + * Minimize generational loss from trancoding. + * Usual cases are handled very efficiently: + o One speaker: That speaker's frame is sent directly to each + participant which uses the same codec. It is trancoded + _once_ for each additional codec type used by participants. + o Two speakers: Each speaker gets the other speaker's frames. + The two speaker's frames are decoded and mixed, and then + encoded _once_ for each codec type used by participants. + +Video features + + * Video passthrough: video from selected member is passed to every + member of the conference. + * Multiple ways to select video + - VAD + - DTMF from conference members + - CLI + * Ability to set default video sources and to lock/unlock video sources. + + +License + +Naturally, app_conference is GPL. The SVN repository also includes parts of +libspeex, which is distributed under a BSD-style license. See LICENSE for more +details. + + +Getting app_conference + +app_conference is available via SVN from its own home on sourceforge: + + * http://sourceforge.net/projects/appconference + + +Compiling app_conference + + * Checkout sources + * Modify Makefile to point to your Asterisk include directory + * make + * sudo make install + + +Using app_conference + +There is no configuration file. Conferences are created on-the-fly. + +Dialplan syntax: Conference(ConferenceName/Flags/Priority[/VADSTART/VADCONTINUE]) + + * ConferenceName: Whatever you want to name the conference + * Flags: please see Flags.txt for a comprehensive list of dialplan flags + * Priority: Currently ignored; was to be a "speaking priority" so a + higher priority caller could "override" others. + * VADSTART: Optional: "probability" to use to detect start of speech. + * VADCONTINUE: Optional: "probability" to use to detect continuation + of speech. + + +CLI Commands + +Please look at CLI.txt for a comprehensive list of CLI commands and parameters. + + +Manager Events + + +app_conference generates several detailed manager events so that applications +interfacing with the manager API can monitor conferences: + + * ConferenceState: sent as members begin/end speaking. + Channel: The channel + State: "speaking" or "silent" + + * ConferenceDTMF: sent when conference members send DTMF to the conference + Channel: The channel + Key: The DTMF key send [0-9*#] + + * ConferenceSoundComplete: send when the conference has finished playing + a sound to a user + Channel: The channel + Sound: The first 255 bytes of the file requested in conference play + sound CLI/Mgr command. + + +Benchmarking + +It would be nice to have solid benchmarks to present, but a good size +machine should be able to handle many callers when either (a) they are +using DTX, or (b) they are listen-only. It's used often with hundreds of +simultaneous callers. + + +Discussion + +The appconference-devel mailing list is the place to discuss everything related +to app_conference. The bug tracker on SourceForge gets a little bit of +attention now and then. + + +--- + +app_conference is brought to you by the letter q, and the number e diff --git a/apps/conference/README.videoswitch b/apps/conference/README.videoswitch new file mode 100644 index 0000000..2612194 --- /dev/null +++ b/apps/conference/README.videoswitch @@ -0,0 +1,87 @@ +VideoSwitch +----------- +(c) Vipadia Limited 2005-2006 +Neil Stratford + +Based on app_conference, see README. + +Including contributions from John Martin + +Example use: + +exten => 2300,1,Videoswitch(test/RX) + +This puts the user into a conference 'test' with the options SRX. + +The options are the same as app_conference, except: + +X - enable the caller to switch video stream using DTMF +R - relay the DTMF to the management interface +C - Mute video - no video from this client +c - No Receive video - send no video to this client +L - Mute audio - no audio from this client +l - No Receive audio - send no audio to this client +M - member is moderator - when they leave everyone else is kicked + +Stream selection options: two integers, first is receive id, second is send id. +Both are optional. + +0-9 - Set initial receive stream to n +0-9 - Set this stream id to n (will stop any other video with that id already) + +eg: Videoswitch(test/01) will set our id to 1, and we will receive id 0's video + +CLI commands (which may also be invoked from the manager interface +using the Command action): + +Most commands have two versions, which can either take a member number (obtained from 'videoswitch list') or a channel identifier (such as SIP/2304-1e82). + +fidwell*CLI> help videoswitch + videoswitch debug enable debugging for a videoswitch + videoswitch kick kick member from a videoswitch + videoswitch list list members of a videoswitch + videoswitch mute mute member in a videoswitch + videoswitch mutechannel mute channel in a videoswitch + videoswitch show stats show videoswitch stats + videoswitch unmute unmute member in a videoswitch +videoswitch unmutechannel unmute channel in a videoswitch + videoswitch viewchannel switch channel in a videoswitch + videoswitch viewstream switch view in a videoswitch + +fidwell*CLI> help videoswitch debug +usage: videoswitch debug [ on | off ] + enable debugging for a videoswitch + +fidwell*CLI> help videoswitch kick +usage: videoswitch kick + kick member form a videoswitch + +fidwell*CLI> help videoswitch list +usage: videoswitch list {} + list members of a videoswitch or list of videoswitches if no name + +fidwell*CLI> help videoswitch mute +usage: videoswitch mute + mute member in a videoswitch + +fidwell*CLI> help videoswitch unmute +usage: videoswitch unmute + unmute member in a videoswitch + +fidwell*CLI> help videoswitch mutechannel +usage: videoswitch mute + mute channel in a videoswitch + +fidwell*CLI> help videoswitch unmutechannel +usage: videoswitch unmute + unmute channel in a videoswitch + +fidwell*CLI> help videoswitch viewchannel +usage: videoswitch viewchannel + channel will receive video stream + +fidwell*CLI> help videoswitch viewstream +usage: videoswitch viewstream + member will receive video stream + + diff --git a/apps/conference/TODO b/apps/conference/TODO new file mode 100644 index 0000000..121e83e --- /dev/null +++ b/apps/conference/TODO @@ -0,0 +1,4 @@ +Things we need to do for the next release +- Enable speex based VAD for all members instead of just for telephone +members (configurable) +- Documentation! diff --git a/apps/conference/app_conference.h b/apps/conference/app_conference.h new file mode 100644 index 0000000..17aca58 --- /dev/null +++ b/apps/conference/app_conference.h @@ -0,0 +1,244 @@ + +// $Id: app_conference.h 839 2007-01-17 22:32:03Z sbalea $ + +/* + * app_conference + * + * A channel independent conference application for Asterisk + * + * Copyright (C) 2002, 2003 Junghanns.NET GmbH + * Copyright (C) 2003, 2004 HorizonLive.com, Inc. + * Copyright (C) 2005, 2006 HorizonWimba, Inc. + * Copyright (C) 2007 Wimba, Inc. + * + * Klaus-Peter Junghanns + * + * Video Conferencing support added by + * Neil Stratford + * Copyright (C) 2005, 2005 Vipadia Limited + * + * VAD driven video conferencing, text message support + * and miscellaneous enhancements added by + * Mihai Balea + * + * This program may be modified and distributed under the + * terms of the GNU General Public License. You should have received + * a copy of the GNU General Public License along with this + * program; if not, write to the Free Software Foundation, Inc. + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _ASTERISK_CONF_H +#define _ASTERISK_CONF_H + + +/* standard includes */ +#include +#include +#include +#include +#include +#include + + +#include + +/* asterisk includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include +#include + + +#if (SILDET == 2) +#include +#endif + +// +// app_conference defines +// + +// debug logging level + +// LOG_NOTICE for debugging, LOG_DEBUG for production +#ifdef APP_CONFERENCE_DEBUG +#define AST_CONF_DEBUG LOG_NOTICE +#else +#define AST_CONF_DEBUG LOG_DEBUG +#endif + +// +// feature defines +// + +// number of times the last non-silent frame should be +// repeated after silence starts +#define AST_CONF_CACHE_LAST_FRAME 1 + +// +// debug defines +// + +//#define DEBUG_USE_TIMELOG + +//#define DEBUG_FRAME_TIMESTAMPS + +// #define DEBUG_OUTPUT_PCM + +// +// !!! THESE CONSTANTS SHOULD BE CLEANED UP AND CLARIFIED !!! +// + +// +// sample information for AST_FORMAT_SLINEAR format +// + +#define AST_CONF_SAMPLE_RATE 8000 +#define AST_CONF_SAMPLE_SIZE 16 +#define AST_CONF_FRAME_INTERVAL 20 +//neils#define AST_CONF_FRAME_INTERVAL 30 + +// +// so, since we cycle approximately every 20ms, +// we can compute the following values: +// +// 160 samples per 20 ms frame -or- +// ( 8000 samples-per-second * ( 20 ms / 1000 ms-per-second ) ) = 160 samples +// +// 320 bytes ( 2560 bits ) of data 20 ms frame -or- +// ( 160 samples * 16 bits-per-sample / 8 bits-per-byte ) = 320 bytes +// + +// 160 samples 16-bit signed linear +#define AST_CONF_BLOCK_SAMPLES 160 + +// 2 bytes per sample ( i.e. 16-bit ) +#define AST_CONF_BYTES_PER_SAMPLE 2 + +// 320 bytes for each 160 sample frame of 16-bit audio +#define AST_CONF_FRAME_DATA_SIZE 320 + +// 1000 ms-per-second / 20 ms-per-frame = 50 frames-per-second +#define AST_CONF_FRAMES_PER_SECOND ( 1000 / AST_CONF_FRAME_INTERVAL ) + + +// +// buffer and queue values +// + +// account for friendly offset when allocating buffer for frame +#define AST_CONF_BUFFER_SIZE ( AST_CONF_FRAME_DATA_SIZE + AST_FRIENDLY_OFFSET ) + +// maximum number of frames queued per member +#define AST_CONF_MAX_QUEUE 100 + +// max video frames in the queue +#define AST_CONF_MAX_VIDEO_QUEUE 800 + +// max dtmf frames in the queue +#define AST_CONF_MAX_DTMF_QUEUE 8 + +// max text frames in the queue +#define AST_CONF_MAX_TEXT_QUEUE 8 + +// minimum number of frames queued per member +#define AST_CONF_MIN_QUEUE 0 + +// number of queued frames before we start dropping +#define AST_CONF_QUEUE_DROP_THRESHOLD 40 + +// number of milliseconds between frame drops +#define AST_CONF_QUEUE_DROP_TIME_LIMIT 750 + +// +// timer and sleep values +// + +// milliseconds we're willing to wait for a channel +// event before we check for outgoing frames +#define AST_CONF_WAITFOR_LATENCY 40 + +// milliseconds to sleep before trying to process frames +#define AST_CONF_CONFERENCE_SLEEP 40 + +// milliseconds to wait between state notification updates +#define AST_CONF_NOTIFICATION_SLEEP 200 + +// +// warning threshold values +// + +// number of frames behind before warning +#define AST_CONF_OUTGOING_FRAMES_WARN 70 + +// number of milliseconds off AST_CONF_FRAME_INTERVAL before warning +#define AST_CONF_INTERVAL_WARNING 1000 + +// +// silence detection values +// + +// toggle silence detection +#define ENABLE_SILENCE_DETECTION 1 + +// silence threshold +#define AST_CONF_SILENCE_THRESHOLD 128 + +// speech tail (delay before dropping silent frames, in ms. +// #define AST_CONF_SPEECH_TAIL 180 + +// number of frames to ignore speex_preprocess() after speech detected +#define AST_CONF_SKIP_SPEEX_PREPROCESS 20 + +// our speex probability values +#define AST_CONF_PROB_START 0.05 +#define AST_CONF_PROB_CONTINUE 0.02 + + +// +// format translation values +// +#ifdef AC_USE_G729A + #define AC_SUPPORTED_FORMATS 6 + enum { AC_SLINEAR_INDEX = 0, AC_ULAW_INDEX, AC_ALAW_INDEX, AC_GSM_INDEX, AC_SPEEX_INDEX, AC_G729A_INDEX } ; +#else + #define AC_SUPPORTED_FORMATS 5 + enum { AC_SLINEAR_INDEX = 0, AC_ULAW_INDEX, AC_ALAW_INDEX, AC_GSM_INDEX, AC_SPEEX_INDEX } ; +#endif + +// +// VAD based video switching parameters +// All time related values are in ms +// + +// Amount of silence required before we decide somebody stopped talking +#define AST_CONF_VIDEO_STOP_TIMEOUT 2000 + +// Amount of audio required before we decide somebody started talking +#define AST_CONF_VIDEO_START_TIMEOUT 2000 + +// +// Text frame control protocol +// +#define AST_CONF_CONTROL_CAMERA_DISABLED "CONTROL:CAMERA_DISABLED" +#define AST_CONF_CONTROL_CAMERA_ENABLED "CONTROL:CAMERA_ENABLED" +#define AST_CONF_CONTROL_START_VIDEO "CONTROL:STARTVIDEO" +#define AST_CONF_CONTROL_STOP_VIDEO "CONTROL:STOPVIDEO" +#define AST_CONF_CONTROL_STOP_VIDEO_TRANSMIT "CONTROL:STOP_VIDEO_TRANSMIT" +#define AST_CONF_CONTROL_START_VIDEO_TRANSMIT "CONTROL:START_VIDEO_TRANSMIT" + +// utility functions +void add_milliseconds( struct timeval* tv, long ms ) ; + +#endif + + diff --git a/apps/conference/cli.c b/apps/conference/cli.c new file mode 100644 index 0000000..3d88ddf --- /dev/null +++ b/apps/conference/cli.c @@ -0,0 +1,1265 @@ + +// $Id: cli.c 884 2007-06-27 14:56:21Z sbalea $ + +/* + * app_conference + * + * A channel independent conference application for Asterisk + * + * Copyright (C) 2002, 2003 Junghanns.NET GmbH + * Copyright (C) 2003, 2004 HorizonLive.com, Inc. + * Copyright (C) 2005, 2006 HorizonWimba, Inc. + * Copyright (C) 2007 Wimba, Inc. + * + * Klaus-Peter Junghanns + * + * Video Conferencing support added by + * Neil Stratford + * Copyright (C) 2005, 2005 Vipadia Limited + * + * VAD driven video conferencing, text message support + * and miscellaneous enhancements added by + * Mihai Balea + * + * This program may be modified and distributed under the + * terms of the GNU General Public License. You should have received + * a copy of the GNU General Public License along with this + * program; if not, write to the Free Software Foundation, Inc. + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "asterisk/autoconfig.h" +#include "cli.h" + +static char conference_restart_usage[] = + "usage: conference restart\n" + " kick all users in all conferences\n" +; + +static struct ast_cli_entry cli_restart = { + { "conference", "restart", NULL }, + conference_restart, + "restart a conference", + conference_restart_usage +} ; + + +int conference_restart( int fd, int argc, char *argv[] ) +{ + if ( argc < 2 ) + return RESULT_SHOWUSAGE ; + + kick_all(); + return RESULT_SUCCESS ; +} + + +// +// debug functions +// + +static char conference_debug_usage[] = + "usage: conference debug [ on | off ]\n" + " enable debugging for a conference\n" +; + +static struct ast_cli_entry cli_debug = { + { "conference", "debug", NULL }, + conference_debug, + "enable debugging for a conference", + conference_debug_usage +} ; + + +int conference_debug( int fd, int argc, char *argv[] ) +{ + if ( argc < 3 ) + return RESULT_SHOWUSAGE ; + + // get the conference name + const char* name = argv[2] ; + + // get the new state + int state = 0 ; + + if ( argc == 3 ) + { + // no state specified, so toggle it + state = -1 ; + } + else + { + if ( strncasecmp( argv[3], "on", 4 ) == 0 ) + state = 1 ; + else if ( strncasecmp( argv[3], "off", 3 ) == 0 ) + state = 0 ; + else + return RESULT_SHOWUSAGE ; + } + + int new_state = set_conference_debugging( name, state ) ; + + if ( new_state == 1 ) + { + ast_cli( fd, "enabled conference debugging, name => %s, new_state => %d\n", + name, new_state ) ; + } + else if ( new_state == 0 ) + { + ast_cli( fd, "disabled conference debugging, name => %s, new_state => %d\n", + name, new_state ) ; + } + else + { + // error setting state + ast_cli( fd, "\nunable to set debugging state, name => %s\n\n", name ) ; + } + + return RESULT_SUCCESS ; +} + +// +// stats functions +// + +static char conference_show_stats_usage[] = + "usage: conference show stats\n" + " display stats for active conferences.\n" +; + +static struct ast_cli_entry cli_show_stats = { + { "conference", "show", "stats", NULL }, + conference_show_stats, + "show conference stats", + conference_show_stats_usage +} ; + +int conference_show_stats( int fd, int argc, char *argv[] ) +{ + if ( argc < 3 ) + return RESULT_SHOWUSAGE ; + + // get count of active conferences + int count = get_conference_count() ; + + ast_cli( fd, "\n\nCONFERENCE STATS, ACTIVE( %d )\n\n", count ) ; + + // if zero, go no further + if ( count <= 0 ) + return RESULT_SUCCESS ; + + // + // get the conference stats + // + + // array of stats structs + ast_conference_stats stats[ count ] ; + + // get stats structs + count = get_conference_stats( stats, count ) ; + + // make sure we were able to fetch some + if ( count <= 0 ) + { + ast_cli( fd, "!!! error fetching conference stats, available => %d !!!\n", count ) ; + return RESULT_SUCCESS ; + } + + // + // output the conference stats + // + + // output header + ast_cli( fd, "%-20.20s %-40.40s\n", "Name", "Stats") ; + ast_cli( fd, "%-20.20s %-40.40s\n", "----", "-----") ; + + ast_conference_stats* s = NULL ; + + int i; + + for ( i = 0 ; i < count ; ++i ) + { + s = &(stats[i]) ; + + // output this conferences stats + ast_cli( fd, "%-20.20s\n", (char*)( &(s->name) )) ; + } + + ast_cli( fd, "\n" ) ; + + // + // drill down to specific stats + // + + if ( argc == 4 ) + { + // show stats for a particular conference + conference_show_stats_name( fd, argv[3] ) ; + } + + return RESULT_SUCCESS ; +} + +int conference_show_stats_name( int fd, const char* name ) +{ + // not implemented yet + return RESULT_SUCCESS ; +} + +static char conference_list_usage[] = + "usage: conference list {}\n" + " list members of a conference\n" +; + +static struct ast_cli_entry cli_list = { + { "conference", "list", NULL }, + conference_list, + "list members of a conference", + conference_list_usage +} ; + + + +int conference_list( int fd, int argc, char *argv[] ) +{ + int index; + + if ( argc < 2 ) + return RESULT_SHOWUSAGE ; + + if (argc >= 3) + { + for (index = 2; index < argc; index++) + { + // get the conference name + const char* name = argv[index] ; + show_conference_list( fd, name ); + } + } + else + { + show_conference_stats(fd); + } + return RESULT_SUCCESS ; +} + + +int conference_kick( int fd, int argc, char *argv[] ) +{ + if ( argc < 4 ) + return RESULT_SHOWUSAGE ; + + // get the conference name + const char* name = argv[2] ; + + int member_id; + sscanf(argv[3], "%d", &member_id); + + int res = kick_member( name, member_id ); + + if (res) ast_cli( fd, "User #: %d kicked\n", member_id) ; + + return RESULT_SUCCESS ; +} + +static char conference_kick_usage[] = + "usage: conference kick \n" + " kick member from conference \n" +; + +static struct ast_cli_entry cli_kick = { + { "conference", "kick", NULL }, + conference_kick, + "kick member from a conference", + conference_kick_usage +} ; + +int conference_kickchannel( int fd, int argc, char *argv[] ) +{ + if ( argc < 4 ) + return RESULT_SHOWUSAGE ; + + const char *name = argv[2] ; + const char *channel = argv[3]; + + int res = kick_channel( name, channel ); + + if ( !res ) + { + ast_cli( fd, "Cannot kick channel %s in conference %s\n", channel, name); + return RESULT_FAILURE; + } + + return RESULT_SUCCESS ; +} + +static char conference_kickchannel_usage[] = + "usage: conference kickchannel \n" + " kick channel from conference\n" +; + +static struct ast_cli_entry cli_kickchannel = { + { "conference", "kickchannel", NULL }, + conference_kickchannel, + "kick channel from conference", + conference_kickchannel_usage +} ; + +int conference_exit( int fd, int argc, char *argv[] ) +{ + if ( argc < 3 ) + return RESULT_SHOWUSAGE ; + + const char *channel = argv[2]; + + struct ast_conf_member *member = find_member(channel, 1); + if(!member) + { + ast_cli(fd, "Member %s not found\n", channel); + return RESULT_FAILURE; + } + const char * name = member->conf_name; + int res = kick_channel( name, channel ); + + if ( !res ) + { + ast_cli(fd, "Cannot exit channel %s from conference %s\n", channel, name); + ast_mutex_unlock(&member->lock); + return RESULT_FAILURE; + } + + ast_mutex_unlock( &member->lock ) ; + return RESULT_SUCCESS ; +} + +static char conference_exit_usage[] = + "usage: conference exit \n" + " exit channel from any conference where it in\n"; + +static struct ast_cli_entry cli_exit = { + { "conference", "exit", NULL }, + conference_exit, + "exit channel from any conference where it in", + conference_exit_usage +}; + +int conference_mute( int fd, int argc, char *argv[] ) +{ + if ( argc < 4 ) + return RESULT_SHOWUSAGE ; + + // get the conference name + const char* name = argv[2] ; + + int member_id; + sscanf(argv[3], "%d", &member_id); + + int res = mute_member( name, member_id ); + + if (res) ast_cli( fd, "User #: %d muted\n", member_id) ; + + return RESULT_SUCCESS ; +} + +static char conference_mute_usage[] = + "usage: conference mute \n" + " mute member in a conference\n" +; + +static struct ast_cli_entry cli_mute = { + { "conference", "mute", NULL }, + conference_mute, + "mute member in a conference", + conference_mute_usage +} ; + +int conference_mutechannel( int fd, int argc, char *argv[] ) +{ + struct ast_conf_member *member; + char *channel; + + if ( argc < 3 ) + return RESULT_SHOWUSAGE ; + + channel = argv[2]; + + member = find_member(channel, 1); + if(!member) { + ast_cli(fd, "Member %s not found\n", channel); + return RESULT_FAILURE; + } + + member->mute_audio = 1; + ast_mutex_unlock( &member->lock ) ; + + ast_cli( fd, "Channel #: %s muted\n", argv[2]) ; + + return RESULT_SUCCESS ; +} + +static char conference_mutechannel_usage[] = + "usage: conference mutechannel \n" + " mute channel in a conference\n" +; + +static struct ast_cli_entry cli_mutechannel = { + { "conference", "mutechannel", NULL }, + conference_mutechannel, + "mute channel in a conference", + conference_mutechannel_usage +} ; + +int conference_viewstream( int fd, int argc, char *argv[] ) +{ + int res; + + if ( argc < 5 ) + return RESULT_SHOWUSAGE ; + + // get the conference name + const char* switch_name = argv[2] ; + + int member_id, viewstream_id; + sscanf(argv[3], "%d", &member_id); + sscanf(argv[4], "%d", &viewstream_id); + + res = viewstream_switch( switch_name, member_id, viewstream_id ); + + if (res) ast_cli( fd, "User #: %d viewing %d\n", member_id, viewstream_id) ; + + return RESULT_SUCCESS ; +} + +static char conference_viewstream_usage[] = + "usage: conference viewstream \n" + " member will receive video stream \n" +; + +static struct ast_cli_entry cli_viewstream = { + { "conference", "viewstream", NULL }, + conference_viewstream, + "switch view in a conference", + conference_viewstream_usage +} ; + +int conference_viewchannel( int fd, int argc, char *argv[] ) +{ + int res; + + if ( argc < 5 ) + return RESULT_SHOWUSAGE ; + + // get the conference name + const char* switch_name = argv[2] ; + + res = viewchannel_switch( switch_name, argv[3], argv[4] ); + + if (res) ast_cli( fd, "Channel #: %s viewing %s\n", argv[3], argv[4]) ; + + return RESULT_SUCCESS ; +} + +static char conference_viewchannel_usage[] = + "usage: conference viewchannel \n" + " channel will receive video stream \n" +; + +static struct ast_cli_entry cli_viewchannel = { + { "conference", "viewchannel", NULL }, + conference_viewchannel, + "switch channel in a conference", + conference_viewchannel_usage +} ; + +int conference_unmute( int fd, int argc, char *argv[] ) +{ + if ( argc < 3 ) + return RESULT_SHOWUSAGE ; + + // get the conference name + const char* name = argv[2] ; + + int member_id; + sscanf(argv[3], "%d", &member_id); + + int res = unmute_member( name, member_id ); + + if (res) ast_cli( fd, "User #: %d unmuted\n", member_id) ; + + return RESULT_SUCCESS ; +} + +static char conference_unmute_usage[] = + "usage: conference unmute \n" + " unmute member in a conference\n" +; + +static struct ast_cli_entry cli_unmute = { + { "conference", "unmute", NULL }, + conference_unmute, + "unmute member in a conference", + conference_unmute_usage +} ; + +int conference_unmutechannel( int fd, int argc, char *argv[] ) +{ + struct ast_conf_member *member; + char *channel; + + if ( argc < 3 ) + return RESULT_SHOWUSAGE ; + + channel = argv[2]; + + member = find_member(channel, 1); + if(!member) { + ast_cli(fd, "Member %s not found\n", channel); + return RESULT_FAILURE; + } + + member->mute_audio = 0; + ast_mutex_unlock( &member->lock ) ; + + ast_cli( fd, "Channel #: %s unmuted\n", argv[2]) ; + + return RESULT_SUCCESS ; +} + +static char conference_unmutechannel_usage[] = + "usage: conference unmutechannel \n" + " unmute channel in a conference\n" +; + +static struct ast_cli_entry cli_unmutechannel = { + { "conference", "unmutechannel", NULL }, + conference_unmutechannel, + "unmute channel in a conference", + conference_unmutechannel_usage +} ; + +// +// play sound +// +static char conference_play_sound_usage[] = + "usage: conference play sound [mute]\n" + " play sound to conference member .\n" + " If mute is specified, all other audio is muted while the sound is played back.\n" +; + +static struct ast_cli_entry cli_play_sound = { + { "conference", "play", "sound", NULL }, + conference_play_sound, + "play a sound to a conference member", + conference_play_sound_usage +} ; + +int conference_play_sound( int fd, int argc, char *argv[] ) +{ + char *channel, *file; + int mute = 0; + + if ( argc < 5 ) + return RESULT_SHOWUSAGE ; + + channel = argv[3]; + file = argv[4]; + + if(argc > 5 && !strcmp(argv[5], "mute")) + mute = 1; + + int res = play_sound_channel(fd, channel, file, mute); + + if ( !res ) + { + ast_cli(fd, "Sound playback failed failed\n"); + return RESULT_FAILURE; + } + return RESULT_SUCCESS ; +} + +// +// stop sounds +// + +static char conference_stop_sounds_usage[] = + "usage: conference stop sounds \n" + " stop sounds for conference member .\n" +; + +static struct ast_cli_entry cli_stop_sounds = { + { "conference", "stop", "sounds", NULL }, + conference_stop_sounds, + "stop sounds for a conference member", + conference_stop_sounds_usage +} ; + +int conference_stop_sounds( int fd, int argc, char *argv[] ) +{ + char *channel; + + if ( argc < 4 ) + return RESULT_SHOWUSAGE ; + + channel = argv[3]; + + int res = stop_sound_channel(fd, channel); + + if ( !res ) + { + ast_cli(fd, "Sound stop failed failed\n"); + return RESULT_FAILURE; + } + return RESULT_SUCCESS ; +} + +// +// end conference +// + +static char conference_end_usage[] = + "usage: conference end \n" + " ends a conference.\n" +; + +static struct ast_cli_entry cli_end = { + { "conference", "end", NULL }, + conference_end, + "stops a conference", + conference_end_usage +} ; + +int conference_end( int fd, int argc, char *argv[] ) +{ + // check the args length + if ( argc < 3 ) + return RESULT_SHOWUSAGE ; + + // conference name + const char* name = argv[2] ; + + // get the conference + if ( end_conference( name, 1 ) != 0 ) + { + ast_cli( fd, "unable to end the conference, name => %s\n", name ) ; + return RESULT_SHOWUSAGE ; + } + + return RESULT_SUCCESS ; +} + +// +// E.BUU - Manager conference end. Additional option to just kick everybody out +// without hangin up channels +// +int manager_conference_end(struct mansession *s, const struct message *m) +{ + const char *confname = astman_get_header(m,"Conference"); + int hangup = 1; + + const char * h = astman_get_header(m, "Hangup"); + if (h) + { + hangup = atoi(h); + } + + ast_log( LOG_NOTICE, "Terminating conference %s on manager's request. Hangup: %s.\n", confname, hangup?"YES":"NO" ); + if ( end_conference( confname, hangup ) != 0 ) + { + ast_log( LOG_ERROR, "manager end conf: unable to terminate conference %s.\n", confname ); + astman_send_error(s, m, "Failed to terminate\r\n"); + return RESULT_FAILURE; + } + + astman_send_ack(s, m, "Conference terminated"); + return RESULT_SUCCESS; +} +// +// lock conference to a video source +// +static char conference_lock_usage[] = + "usage: conference lock \n" + " locks incoming video stream for conference to member \n" +; + +static struct ast_cli_entry cli_lock = { + { "conference", "lock", NULL }, + conference_lock, + "locks incoming video to a member", + conference_lock_usage +} ; + +int conference_lock( int fd, int argc, char *argv[] ) +{ + // check args + if ( argc < 4 ) + return RESULT_SHOWUSAGE; + + const char *conference = argv[2]; + int member; + sscanf(argv[3], "%d", &member); + + int res = lock_conference(conference, member); + + if ( !res ) + { + ast_cli(fd, "Locking failed\n"); + return RESULT_FAILURE; + } + + return RESULT_SUCCESS; +} + +// +// lock conference to a video source channel +// +static char conference_lockchannel_usage[] = + "usage: conference lockchannel \n" + " locks incoming video stream for conference to channel \n" +; + +static struct ast_cli_entry cli_lockchannel = { + { "conference", "lockchannel", NULL }, + conference_lockchannel, + "locks incoming video to a channel", + conference_lockchannel_usage +} ; + +int conference_lockchannel( int fd, int argc, char *argv[] ) +{ + // check args + if ( argc < 4 ) + return RESULT_SHOWUSAGE; + + const char *conference = argv[2]; + const char *channel = argv[3]; + + int res = lock_conference_channel(conference, channel); + + if ( !res ) + { + ast_cli(fd, "Locking failed\n"); + return RESULT_FAILURE; + } + + return RESULT_SUCCESS; +} + +// +// unlock conference +// +static char conference_unlock_usage[] = + "usage: conference unlock \n" + " unlocks conference \n" +; + +static struct ast_cli_entry cli_unlock = { + { "conference", "unlock", NULL }, + conference_unlock, + "unlocks conference", + conference_unlock_usage +} ; + +int conference_unlock( int fd, int argc, char *argv[] ) +{ + // check args + if ( argc < 3 ) + return RESULT_SHOWUSAGE; + + + const char *conference = argv[2]; + + int res = unlock_conference(conference); + + if ( !res ) + { + ast_cli(fd, "Unlocking failed\n"); + return RESULT_FAILURE; + } + + return RESULT_SUCCESS; +} + +// +// Set conference default video source +// +static char conference_set_default_usage[] = + "usage: conference set default \n" + " sets the default video source for conference to member \n" + " Use a negative value for member if you want to clear the default\n" +; + +static struct ast_cli_entry cli_set_default = { + { "conference", "set", "default", NULL }, + conference_set_default, + "sets default video source", + conference_set_default_usage +} ; + +int conference_set_default(int fd, int argc, char *argv[] ) +{ + // check args + if ( argc < 5 ) + return RESULT_SHOWUSAGE; + + const char *conference = argv[3]; + int member; + sscanf(argv[4], "%d", &member); + + int res = set_default_id(conference, member); + + if ( !res ) + { + ast_cli(fd, "Setting default video id failed\n"); + return RESULT_FAILURE; + } + + return RESULT_SUCCESS; +} + +// +// Set conference default video source channel +// +static char conference_set_defaultchannel_usage[] = + "usage: conference set defaultchannel \n" + " sets the default video source channel for conference to channel \n" +; + +static struct ast_cli_entry cli_set_defaultchannel = { + { "conference", "set", "defaultchannel", NULL }, + conference_set_defaultchannel, + "sets default video source channel", + conference_set_defaultchannel_usage +} ; + +int conference_set_defaultchannel(int fd, int argc, char *argv[] ) +{ + // check args + if ( argc < 5 ) + return RESULT_SHOWUSAGE; + + const char *conference = argv[3]; + const char *channel = argv[4]; + + int res = set_default_channel(conference, channel); + + if ( !res ) + { + ast_cli(fd, "Setting default video id failed\n"); + return RESULT_FAILURE; + } + + return RESULT_SUCCESS; +} + +// +// Mute video from a member +// +static char conference_video_mute_usage[] = + "usage: conference video mute \n" + " mutes video from member in conference \n" +; + +static struct ast_cli_entry cli_video_mute = { + { "conference", "video", "mute", NULL }, + conference_video_mute, + "mutes video from a member", + conference_video_mute_usage +} ; + +int conference_video_mute(int fd, int argc, char *argv[] ) +{ + // check args + if ( argc < 5 ) + return RESULT_SHOWUSAGE; + + const char *conference = argv[3]; + int member; + sscanf(argv[4], "%d", &member); + + int res = video_mute_member(conference, member); + + if ( !res ) + { + ast_cli(fd, "Muting video from member %d failed\n", member); + return RESULT_FAILURE; + } + + return RESULT_SUCCESS; +} + +// +// Unmute video from a member +// +static char conference_video_unmute_usage[] = + "usage: conference video unmute \n" + " unmutes video from member in conference \n" +; + +static struct ast_cli_entry cli_video_unmute = { + { "conference", "video", "unmute", NULL }, + conference_video_unmute, + "unmutes video from a member", + conference_video_unmute_usage +} ; + +int conference_video_unmute(int fd, int argc, char *argv[] ) +{ + // check args + if ( argc < 5 ) + return RESULT_SHOWUSAGE; + + const char *conference = argv[3]; + int member; + sscanf(argv[4], "%d", &member); + + int res = video_unmute_member(conference, member); + + if ( !res ) + { + ast_cli(fd, "Unmuting video from member %d failed\n", member); + return RESULT_FAILURE; + } + + return RESULT_SUCCESS; +} + +// +// Mute video from a channel +// +static char conference_video_mutechannel_usage[] = + "usage: conference video mutechannel \n" + " mutes video from channel in conference \n" +; + +static struct ast_cli_entry cli_video_mutechannel = { + { "conference", "video", "mutechannel", NULL }, + conference_video_mutechannel, + "mutes video from a channel", + conference_video_mutechannel_usage +} ; + +int conference_video_mutechannel(int fd, int argc, char *argv[] ) +{ + // check args + if ( argc < 5 ) + return RESULT_SHOWUSAGE; + + const char *conference = argv[3]; + const char *channel = argv[4]; + + int res = video_mute_channel(conference, channel); + + if ( !res ) + { + ast_cli(fd, "Muting video from channel %s failed\n", channel); + return RESULT_FAILURE; + } + + return RESULT_SUCCESS; +} + +// +// Unmute video from a channel +// +static char conference_video_unmutechannel_usage[] = + "usage: conference video unmutechannel \n" + " unmutes video from channel in conference \n" +; + +static struct ast_cli_entry cli_video_unmutechannel = { + { "conference", "video", "unmutechannel", NULL }, + conference_video_unmutechannel, + "unmutes video from a channel", + conference_video_unmutechannel_usage +} ; + +int conference_video_unmutechannel(int fd, int argc, char *argv[] ) +{ + // check args + if ( argc < 5 ) + return RESULT_SHOWUSAGE; + + const char *conference = argv[3]; + const char *channel = argv[4]; + + int res = video_unmute_channel(conference, channel); + + if ( !res ) + { + ast_cli(fd, "Unmuting video from channel %s failed\n", channel); + return RESULT_FAILURE; + } + + return RESULT_SUCCESS; +} + + +// +// Text message functions +// Send a text message to a member +// +static char conference_text_usage[] = + "usage: conference text \n" + " Sends text message to member in conference \n" +; + +static struct ast_cli_entry cli_text = { + { "conference", "text", NULL }, + conference_text, + "sends a text message to a member", + conference_text_usage +} ; + +int conference_text(int fd, int argc, char *argv[] ) +{ + // check args + if ( argc < 5 ) + return RESULT_SHOWUSAGE; + + const char *conference = argv[2]; + int member; + sscanf(argv[3], "%d", &member); + const char *text = argv[4]; + + int res = send_text(conference, member, text); + + if ( !res ) + { + ast_cli(fd, "Sending a text message to member %d failed\n", member); + return RESULT_FAILURE; + } + + return RESULT_SUCCESS; +} + +// +// Send a text message to a channel +// +static char conference_textchannel_usage[] = + "usage: conference textchannel \n" + " Sends text message to channel in conference \n" +; + +static struct ast_cli_entry cli_textchannel = { + { "conference", "textchannel", NULL }, + conference_textchannel, + "sends a text message to a channel", + conference_textchannel_usage +} ; + +int conference_textchannel(int fd, int argc, char *argv[] ) +{ + // check args + if ( argc < 5 ) + return RESULT_SHOWUSAGE; + + const char *conference = argv[2]; + const char *channel = argv[3]; + const char *text = argv[4]; + + int res = send_text_channel(conference, channel, text); + + if ( !res ) + { + ast_cli(fd, "Sending a text message to channel %s failed\n", channel); + return RESULT_FAILURE; + } + + return RESULT_SUCCESS; +} + +// +// Send a text message to all members in a conference +// +static char conference_textbroadcast_usage[] = + "usage: conference textbroadcast \n" + " Sends text message to all members in conference \n" +; + +static struct ast_cli_entry cli_textbroadcast = { + { "conference", "textbroadcast", NULL }, + conference_textbroadcast, + "sends a text message to all members in a conference", + conference_textbroadcast_usage +} ; + +int conference_textbroadcast(int fd, int argc, char *argv[] ) +{ + // check args + if ( argc < 4 ) + return RESULT_SHOWUSAGE; + + const char *conference = argv[2]; + const char *text = argv[3]; + + int res = send_text_broadcast(conference, text); + + if ( !res ) + { + ast_cli(fd, "Sending a text broadcast to conference %s failed\n", conference); + return RESULT_FAILURE; + } + + return RESULT_SUCCESS; +} + +// +// Associate two members +// Audio from the source member will drive VAD based video switching for the destination member +// If the destination member is missing or negative, break any existing association +// +static char conference_drive_usage[] = + "usage: conference drive [destination member]\n" + " Drives VAD video switching of using audio from in conference \n" + " If destination is missing or negative, break existing association\n" +; + +static struct ast_cli_entry cli_drive = { + { "conference", "drive", NULL }, + conference_drive, + "pairs two members to drive VAD-based video switching", + conference_drive_usage +} ; + +int conference_drive(int fd, int argc, char *argv[] ) +{ + // check args + if ( argc < 4 ) + return RESULT_SHOWUSAGE; + + const char *conference = argv[2]; + int src_member = -1; + int dst_member = -1; + sscanf(argv[3], "%d", &src_member); + if ( argc > 4 ) + sscanf(argv[4], "%d", &dst_member); + + int res = drive(conference, src_member, dst_member); + + if ( !res ) + { + ast_cli(fd, "Pairing members %d and %d failed\n", src_member, dst_member); + return RESULT_FAILURE; + } + + return RESULT_SUCCESS; +} + +// +// Associate two channels +// Audio from the source channel will drive VAD based video switching for the destination channel +// If the destination channel is missing, break any existing association +// +static char conference_drivechannel_usage[] = + "usage: conference drive [destination channel]\n" + " Drives VAD video switching of using audio from in conference \n" + " If destination is missing, break existing association\n" +; + +static struct ast_cli_entry cli_drivechannel = { + { "conference", "drivechannel", NULL }, + conference_drivechannel, + "pairs two channels to drive VAD-based video switching", + conference_drivechannel_usage +} ; + +int conference_drivechannel(int fd, int argc, char *argv[] ) +{ + // check args + if ( argc < 4 ) + return RESULT_SHOWUSAGE; + + const char *conference = argv[2]; + const char *src_channel = argv[3]; + const char *dst_channel = NULL; + if ( argc > 4 ) + dst_channel = argv[4]; + + int res = drive_channel(conference, src_channel, dst_channel); + + if ( !res ) + { + ast_cli(fd, "Pairing channels %s and %s failed\n", src_channel, dst_channel); + return RESULT_FAILURE; + } + + return RESULT_SUCCESS; +} + + +// +// cli initialization function +// + +void register_conference_cli( void ) +{ + ast_cli_register( &cli_restart ); + ast_cli_register( &cli_debug ) ; + ast_cli_register( &cli_show_stats ) ; + ast_cli_register( &cli_list ); + ast_cli_register( &cli_kick ); + ast_cli_register( &cli_kickchannel ); + ast_cli_register( &cli_exit ); + ast_cli_register( &cli_mute ); + ast_cli_register( &cli_mutechannel ); + ast_cli_register( &cli_viewstream ); + ast_cli_register( &cli_viewchannel ); + ast_cli_register( &cli_unmute ); + ast_cli_register( &cli_unmutechannel ); + ast_cli_register( &cli_play_sound ) ; + ast_cli_register( &cli_stop_sounds ) ; + ast_cli_register( &cli_end ); + ast_cli_register( &cli_lock ); + ast_cli_register( &cli_lockchannel ); + ast_cli_register( &cli_unlock ); + ast_cli_register( &cli_set_default ); + ast_cli_register( &cli_set_defaultchannel ); + ast_cli_register( &cli_video_mute ) ; + ast_cli_register( &cli_video_unmute ) ; + ast_cli_register( &cli_video_mutechannel ) ; + ast_cli_register( &cli_video_unmutechannel ) ; + ast_cli_register( &cli_text ); + ast_cli_register( &cli_textchannel ); + ast_cli_register( &cli_textbroadcast ); + ast_cli_register( &cli_drive ); + ast_cli_register( &cli_drivechannel ); + ast_manager_register( "ConferenceList", 0, manager_conference_list, "Conference List" ); + ast_manager_register( "ConferenceEnd", EVENT_FLAG_CALL, manager_conference_end, "Terminate a conference" ); + +} + +void unregister_conference_cli( void ) +{ + ast_cli_unregister( &cli_restart ); + ast_cli_unregister( &cli_debug ) ; + ast_cli_unregister( &cli_show_stats ) ; + ast_cli_unregister( &cli_list ); + ast_cli_unregister( &cli_kick ); + ast_cli_unregister( &cli_kickchannel ); + ast_cli_unregister( &cli_exit ); + ast_cli_unregister( &cli_mute ); + ast_cli_unregister( &cli_mutechannel ); + ast_cli_unregister( &cli_viewstream ); + ast_cli_unregister( &cli_viewchannel ); + ast_cli_unregister( &cli_unmute ); + ast_cli_unregister( &cli_unmutechannel ); + ast_cli_unregister( &cli_play_sound ) ; + ast_cli_unregister( &cli_stop_sounds ) ; + ast_cli_unregister( &cli_end ); + ast_cli_unregister( &cli_lock ); + ast_cli_unregister( &cli_lockchannel ); + ast_cli_unregister( &cli_unlock ); + ast_cli_unregister( &cli_set_default ); + ast_cli_unregister( &cli_set_defaultchannel ); + ast_cli_unregister( &cli_video_mute ) ; + ast_cli_unregister( &cli_video_unmute ) ; + ast_cli_unregister( &cli_video_mutechannel ) ; + ast_cli_unregister( &cli_video_unmutechannel ) ; + ast_cli_unregister( &cli_text ); + ast_cli_unregister( &cli_textchannel ); + ast_cli_unregister( &cli_textbroadcast ); + ast_cli_unregister( &cli_drive ); + ast_cli_unregister( &cli_drivechannel ); + ast_manager_unregister( "ConferenceList" ); + ast_manager_unregister( "ConferenceEnd" ); +} diff --git a/apps/conference/cli.h b/apps/conference/cli.h new file mode 100644 index 0000000..34b79d6 --- /dev/null +++ b/apps/conference/cli.h @@ -0,0 +1,99 @@ + +// $Id: cli.h 880 2007-04-25 15:23:59Z jpgrayson $ + +/* + * app_conference + * + * A channel independent conference application for Asterisk + * + * Copyright (C) 2002, 2003 Junghanns.NET GmbH + * Copyright (C) 2003, 2004 HorizonLive.com, Inc. + * Copyright (C) 2005, 2006 HorizonWimba, Inc. + * Copyright (C) 2007 Wimba, Inc. + * + * Klaus-Peter Junghanns + * + * Video Conferencing support added by + * Neil Stratford + * Copyright (C) 2005, 2005 Vipadia Limited + * + * VAD driven video conferencing, text message support + * and miscellaneous enhancements added by + * Mihai Balea + * + * This program may be modified and distributed under the + * terms of the GNU General Public License. You should have received + * a copy of the GNU General Public License along with this + * program; if not, write to the Free Software Foundation, Inc. + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _APP_CONF_CLI_H +#define _APP_CONF_CLI_H + +// +// includes +// + +#include "app_conference.h" +#include "common.h" + +// +// function declarations +// + +int conference_show_stats( int fd, int argc, char *argv[] ) ; +int conference_show_stats_name( int fd, const char* name ) ; + +int conference_restart( int fd, int argc, char *argv[] ); + +int conference_debug( int fd, int argc, char *argv[] ) ; +int conference_no_debug( int fd, int argc, char *argv[] ) ; + +int conference_list( int fd, int argc, char *argv[] ) ; +int conference_kick( int fd, int argc, char *argv[] ) ; +int conference_kickchannel( int fd, int argc, char *argv[] ) ; + +int conference_exit( int fd, int argc, char *argv[] ) ; + +int conference_mute( int fd, int argc, char *argv[] ) ; +int conference_unmute( int fd, int argc, char *argv[] ) ; +int conference_mutechannel( int fd, int argc, char *argv[] ) ; +int conference_unmutechannel( int fd, int argc, char *argv[] ) ; +int conference_viewstream( int fd, int argc, char *argv[] ) ; +int conference_viewchannel( int fd, int argc, char *argv[] ) ; + +int conference_play_sound( int fd, int argc, char *argv[] ) ; +int conference_stop_sounds( int fd, int argc, char *argv[] ) ; + +int conference_play_video( int fd, int argc, char *argv[] ) ; +int conference_stop_videos( int fd, int argc, char *argv[] ) ; + +int conference_end( int fd, int argc, char *argv[] ) ; + +int conference_lock( int fd, int argc, char *argv[] ) ; +int conference_lockchannel( int fd, int argc, char *argv[] ) ; +int conference_unlock( int fd, int argc, char *argv[] ) ; + +int conference_set_default(int fd, int argc, char *argv[] ) ; +int conference_set_defaultchannel(int fd, int argc, char *argv[] ) ; + +int conference_video_mute(int fd, int argc, char *argv[] ) ; +int conference_video_mutechannel(int fd, int argc, char *argv[] ) ; +int conference_video_unmute(int fd, int argc, char *argv[] ) ; +int conference_video_unmutechannel(int fd, int argc, char *argv[] ) ; + +int conference_text( int fd, int argc, char *argv[] ) ; +int conference_textchannel( int fd, int argc, char *argv[] ) ; +int conference_textbroadcast( int fd, int argc, char *argv[] ) ; + +int conference_drive( int fd, int argc, char *argv[] ) ; +int conference_drivechannel(int fd, int argc, char *argv[] ); + +int manager_conference_end(struct mansession *s, const struct message *m); + +void register_conference_cli( void ) ; +void unregister_conference_cli( void ) ; + + +#endif diff --git a/apps/conference/common.h b/apps/conference/common.h new file mode 100644 index 0000000..989398c --- /dev/null +++ b/apps/conference/common.h @@ -0,0 +1,63 @@ + +// $Id: common.h 880 2007-04-25 15:23:59Z jpgrayson $ + +/* + * app_conference + * + * A channel independent conference application for Asterisk + * + * Copyright (C) 2002, 2003 Junghanns.NET GmbH + * Copyright (C) 2003, 2004 HorizonLive.com, Inc. + * Copyright (C) 2005, 2006 HorizonWimba, Inc. + * Copyright (C) 2007 Wimba, Inc. + * + * Klaus-Peter Junghanns + * + * Video Conferencing support added by + * Neil Stratford + * Copyright (C) 2005, 2005 Vipadia Limited + * + * VAD driven video conferencing, text message support + * and miscellaneous enhancements added by + * Mihai Balea + * + * This program may be modified and distributed under the + * terms of the GNU General Public License. You should have received + * a copy of the GNU General Public License along with this + * program; if not, write to the Free Software Foundation, Inc. + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _APP_CONF_COMMON_H +#define _APP_CONF_COMMON_H + +#include + +// typedef includes +#include "conf_frame.h" + +// function includesee +//#include "member.h" +#include "conference.h" +#include "frame.h" +#include "cli.h" + +/* Utility functions */ + +/* LOG the time taken to execute a function (like lock acquisition */ +#if 1 +#define TIMELOG(func,min,message) \ + do { \ + struct timeval t1, t2; \ + int diff; \ + t1 = ast_tvnow(); \ + func; \ + t2 = ast_tvnow(); \ + if ( (diff = ast_tvdiff_ms(t2, t1)) > min ) \ + ast_log( AST_CONF_DEBUG, "TimeLog: %s: %d ms\n", message, diff); \ + } while (0) +#else +#define TIMELOG(func,min,message) func +#endif + +#endif diff --git a/apps/conference/conf_frame.h b/apps/conference/conf_frame.h new file mode 100644 index 0000000..e73e57a --- /dev/null +++ b/apps/conference/conf_frame.h @@ -0,0 +1,73 @@ + +// $Id: conf_frame.h 880 2007-04-25 15:23:59Z jpgrayson $ + +/* + * app_conference + * + * A channel independent conference application for Asterisk + * + * Copyright (C) 2002, 2003 Junghanns.NET GmbH + * Copyright (C) 2003, 2004 HorizonLive.com, Inc. + * Copyright (C) 2005, 2006 HorizonWimba, Inc. + * Copyright (C) 2007 Wimba, Inc. + * + * Klaus-Peter Junghanns + * + * Video Conferencing support added by + * Neil Stratford + * Copyright (C) 2005, 2005 Vipadia Limited + * + * VAD driven video conferencing, text message support + * and miscellaneous enhancements added by + * Mihai Balea + * + * This program may be modified and distributed under the + * terms of the GNU General Public License. You should have received + * a copy of the GNU General Public License along with this + * program; if not, write to the Free Software Foundation, Inc. + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _APP_CONF_STRUCTS_H +#define _APP_CONF_STRUCTS_H + +// +// includes +// + +#include "app_conference.h" +#include "common.h" + +// +// struct declarations +// + +typedef struct conf_frame +{ + // frame audio data + struct ast_frame* fr ; + + // array of converted versions for listeners + struct ast_frame* converted[ AC_SUPPORTED_FORMATS ] ; + + // pointer to the frame's owner + struct ast_conf_member* member ; // who sent this frame + + // frame meta data +// struct timeval timestamp ; +// unsigned long cycleid ; +// int priority ; + + // linked-list pointers + struct conf_frame* next ; + struct conf_frame* prev ; + + // should this frame be preserved + short static_frame ; + + // pointer to mixing buffer + char* mixed_buffer ; +} conf_frame ; + + +#endif diff --git a/apps/conference/conference.c b/apps/conference/conference.c new file mode 100644 index 0000000..72c71d9 --- /dev/null +++ b/apps/conference/conference.c @@ -0,0 +1,2923 @@ + +// $Id: conference.c 886 2007-08-06 14:33:34Z bcholew $ + +/* + * app_conference + * + * A channel independent conference application for Asterisk + * + * Copyright (C) 2002, 2003 Junghanns.NET GmbH + * Copyright (C) 2003, 2004 HorizonLive.com, Inc. + * Copyright (C) 2005, 2006 HorizonWimba, Inc. + * Copyright (C) 2007 Wimba, Inc. + * + * Klaus-Peter Junghanns + * + * Video Conferencing support added by + * Neil Stratford + * Copyright (C) 2005, 2005 Vipadia Limited + * + * VAD driven video conferencing, text message support + * and miscellaneous enhancements added by + * Mihai Balea + * + * This program may be modified and distributed under the + * terms of the GNU General Public License. You should have received + * a copy of the GNU General Public License along with this + * program; if not, write to the Free Software Foundation, Inc. + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "asterisk/autoconfig.h" +#include "conference.h" +#include "asterisk/utils.h" + +// +// static variables +// + +// single-linked list of current conferences +struct ast_conference *conflist = NULL ; + +// mutex for synchronizing access to conflist +//static ast_mutex_t conflist_lock = AST_MUTEX_INITIALIZER ; +AST_MUTEX_DEFINE_STATIC(conflist_lock); + +static int conference_count = 0 ; + + +// +// main conference function +// + +void conference_exec( struct ast_conference *conf ) +{ + + struct ast_conf_member *next_member; + struct ast_conf_member *member, *video_source_member, *dtmf_source_member;; + struct conf_frame *cfr, *spoken_frames, *send_frames; + + // count number of speakers, number of listeners + int speaker_count ; + int listener_count ; + + ast_log( AST_CONF_DEBUG, "Entered conference_exec, name => %s\n", conf->name ) ; + + // timer timestamps + struct timeval base, curr, notify ; + base = notify = ast_tvnow(); + + // holds differences of curr and base + long time_diff = 0 ; + long time_sleep = 0 ; + + int since_last_slept = 0 ; + + // + // variables for checking thread frequency + // + + // count to AST_CONF_FRAMES_PER_SECOND + int tf_count = 0 ; + long tf_diff = 0 ; + float tf_frequency = 0.0 ; + + struct timeval tf_base, tf_curr ; + tf_base = ast_tvnow(); + + // + // main conference thread loop + // + + + while ( 42 == 42 ) + { + // update the current timestamp + curr = ast_tvnow(); + + // calculate difference in timestamps + time_diff = ast_tvdiff_ms(curr, base); + + // calculate time we should sleep + time_sleep = AST_CONF_FRAME_INTERVAL - time_diff ; + + if ( time_sleep > 0 ) + { + // sleep for sleep_time ( as milliseconds ) + usleep( time_sleep * 1000 ) ; + + // reset since last slept counter + since_last_slept = 0 ; + + continue ; + } + else + { + // long sleep warning + if ( + since_last_slept == 0 + && time_diff > AST_CONF_CONFERENCE_SLEEP * 2 + ) + { + ast_log( + AST_CONF_DEBUG, + "long scheduling delay, time_diff => %ld, AST_CONF_FRAME_INTERVAL => %d\n", + time_diff, AST_CONF_FRAME_INTERVAL + ) ; + } + + // increment times since last slept + ++since_last_slept ; + + // sleep every other time + if ( since_last_slept % 2 ) + usleep( 0 ) ; + } + + // adjust the timer base ( it will be used later to timestamp outgoing frames ) + add_milliseconds( &base, AST_CONF_FRAME_INTERVAL ) ; + + // + // check thread frequency + // + + if ( ++tf_count >= AST_CONF_FRAMES_PER_SECOND ) + { + // update current timestamp + tf_curr = ast_tvnow(); + + // compute timestamp difference + tf_diff = ast_tvdiff_ms(tf_curr, tf_base); + + // compute sampling frequency + tf_frequency = ( float )( tf_diff ) / ( float )( tf_count ) ; + + if ( + ( tf_frequency <= ( float )( AST_CONF_FRAME_INTERVAL - 1 ) ) + || ( tf_frequency >= ( float )( AST_CONF_FRAME_INTERVAL + 1 ) ) + ) + { + ast_log( + LOG_WARNING, + "processed frame frequency variation, name => %s, tf_count => %d, tf_diff => %ld, tf_frequency => %2.4f\n", + conf->name, tf_count, tf_diff, tf_frequency + ) ; + } + + // reset values + tf_base = tf_curr ; + tf_count = 0 ; + } + + //-----------------// + // INCOMING FRAMES // + //-----------------// + + // ast_log( AST_CONF_DEBUG, "PROCESSING FRAMES, conference => %s, step => %d, ms => %ld\n", + // conf->name, step, ( base.tv_usec / 20000 ) ) ; + + // + // check if the conference is empty and if so + // remove it and break the loop + // + + // acquire the conference list lock + ast_mutex_lock(&conflist_lock); + + // acquire the conference mutex + ast_mutex_lock(&conf->lock); + + if ( conf->membercount == 0 ) + { + if (conf->debug_flag) + { + ast_log( LOG_NOTICE, "removing conference, count => %d, name => %s\n", conf->membercount, conf->name ) ; + } + remove_conf( conf ) ; // stop the conference + + // We don't need to release the conf mutex, since it was destroyed anyway + + // release the conference list lock + ast_mutex_unlock(&conflist_lock); + + break ; // break from main processing loop + } + + // release the conference mutex + ast_mutex_unlock(&conf->lock); + + // release the conference list lock + ast_mutex_unlock(&conflist_lock); + + + // + // Start processing frames + // + + // acquire conference mutex + TIMELOG(ast_mutex_lock( &conf->lock ),1,"conf thread conf lock"); + + if ( conf->membercount == 0 ) + { + // release the conference mutex + ast_mutex_unlock(&conf->lock); + continue; // We'll check again at the top of the loop + } + + // update the current delivery time + conf->delivery_time = base ; + + // + // loop through the list of members + // ( conf->memberlist is a single-linked list ) + // + + // ast_log( AST_CONF_DEBUG, "begin processing incoming audio, name => %s\n", conf->name ) ; + + // reset speaker and listener count + speaker_count = 0 ; + listener_count = 0 ; + + // get list of conference members + member = conf->memberlist ; + + // reset pointer lists + spoken_frames = NULL ; + + // reset video source + video_source_member = NULL; + + // reset dtmf source + dtmf_source_member = NULL; + + // loop over member list to retrieve queued frames + while ( member != NULL ) + { + // take note of next member - before it's too late + next_member = member->next; + + // this MIGHT delete member + member_process_spoken_frames(conf,member,&spoken_frames,time_diff, + &listener_count, &speaker_count); + + // adjust our pointer to the next inline + member = next_member; + } + + // ast_log( AST_CONF_DEBUG, "finished processing incoming audio, name => %s\n", conf->name ) ; + + + //---------------// + // MIXING FRAMES // + //---------------// + + // mix frames and get batch of outgoing frames + send_frames = mix_frames( spoken_frames, speaker_count, listener_count ) ; + + // accounting: if there are frames, count them as one incoming frame + if ( send_frames != NULL ) + { + // set delivery timestamp + //set_conf_frame_delivery( send_frames, base ) ; +// ast_log ( LOG_WARNING, "base = %d,%d: conf->delivery_time = %d,%d\n",base.tv_sec,base.tv_usec, conf->delivery_time.tv_sec, conf->delivery_time.tv_usec); + + // ast_log( AST_CONF_DEBUG, "base => %ld.%ld %d\n", base.tv_sec, base.tv_usec, ( int )( base.tv_usec / 1000 ) ) ; + + conf->stats.frames_in++ ; + } + + //-----------------// + // OUTGOING FRAMES // + //-----------------// + + // + // loop over member list to queue outgoing frames + // + for ( member = conf->memberlist ; member != NULL ; member = member->next ) + { + member_process_outgoing_frames(conf, member, send_frames); + } + + //-------// + // VIDEO // + //-------// + + // loop over the incoming frames and send to all outgoing + // TODO: this is an O(n^2) algorithm. Can we speed it up without sacrificing per-member switching? + for (video_source_member = conf->memberlist; + video_source_member != NULL; + video_source_member = video_source_member->next) + { + while ((cfr = get_incoming_video_frame( video_source_member ))) + { + for (member = conf->memberlist; member != NULL; member = member->next) + { + // skip members that are not ready or are not supposed to receive video + if ( !member->ready_for_outgoing || member->norecv_video ) + continue ; + + if ( conf->video_locked ) + { + // Always send video from the locked source + if ( conf->current_video_source_id == video_source_member->id ) + queue_outgoing_video_frame(member, cfr->fr, conf->delivery_time); + } else + { + // If the member has vad switching disabled and dtmf switching enabled, use that + if ( member->dtmf_switch && + !member->vad_switch && + member->req_id == video_source_member->id + ) + { + queue_outgoing_video_frame(member, cfr->fr, conf->delivery_time); + } else + { + // If no dtmf switching, then do VAD switching + // The VAD switching decision code should make sure that our video source + // is legit + if ( (conf->current_video_source_id == video_source_member->id) || + (conf->current_video_source_id < 0 && + conf->default_video_source_id == video_source_member->id + ) + ) + { + queue_outgoing_video_frame(member, cfr->fr, conf->delivery_time); + } + } + + + } + } + // Garbage collection + delete_conf_frame(cfr); + } + } + + //------// + // DTMF // + //------// + + // loop over the incoming frames and send to all outgoing + for (dtmf_source_member = conf->memberlist; dtmf_source_member != NULL; dtmf_source_member = dtmf_source_member->next) + { + while ((cfr = get_incoming_dtmf_frame( dtmf_source_member ))) + { + for (member = conf->memberlist; member != NULL; member = member->next) + { + // skip members that are not ready + if ( member->ready_for_outgoing == 0 ) + { + continue ; + } + + if (member != dtmf_source_member) + { + // Send the latest frame + queue_outgoing_dtmf_frame(member, cfr->fr); + } + } + // Garbage collection + delete_conf_frame(cfr); + } + } + + //---------// + // CLEANUP // + //---------// + + // clean up send frames + while ( send_frames != NULL ) + { + // accouting: count all frames and mixed frames + if ( send_frames->member == NULL ) + conf->stats.frames_out++ ; + else + conf->stats.frames_mixed++ ; + + // delete the frame + send_frames = delete_conf_frame( send_frames ) ; + } + + // + // notify the manager of state changes every 100 milliseconds + // we piggyback on this for VAD switching logic + // + + if ( ( ast_tvdiff_ms(curr, notify) / AST_CONF_NOTIFICATION_SLEEP ) >= 1 ) + { + // Do VAD switching logic + // We need to do this here since send_state_change_notifications + // resets the flags + if ( !conf->video_locked ) + do_VAD_switching(conf); + + // send the notifications + send_state_change_notifications( conf->memberlist ) ; + + // increment the notification timer base + add_milliseconds( ¬ify, AST_CONF_NOTIFICATION_SLEEP ) ; + } + + // release conference mutex + ast_mutex_unlock( &conf->lock ) ; + + // !!! TESTING !!! + // usleep( 1 ) ; + } + // end while ( 42 == 42 ) + + // + // exit the conference thread + // + + ast_log( AST_CONF_DEBUG, "exit conference_exec\n" ) ; + + // exit the thread + pthread_exit( NULL ) ; + + return ; +} + +// +// manange conference functions +// + +// called by app_conference.c:load_module() +void init_conference( void ) +{ + ast_mutex_init( &conflist_lock ) ; +} + +struct ast_conference* start_conference( struct ast_conf_member* member ) +{ + // check input + if ( member == NULL ) + { + ast_log( LOG_WARNING, "unable to handle null member\n" ) ; + return NULL ; + } + + struct ast_conference* conf = NULL ; + + // acquire the conference list lock + ast_mutex_lock(&conflist_lock); + + + + // look for an existing conference + ast_log( AST_CONF_DEBUG, "attempting to find requested conference\n" ) ; + conf = find_conf( member->conf_name ) ; + + // unable to find an existing conference, try to create one + if ( conf == NULL ) + { + // create a new conference + ast_log( AST_CONF_DEBUG, "attempting to create requested conference\n" ) ; + + // create the new conference with one member + conf = create_conf( member->conf_name, member ) ; + + // return an error if create_conf() failed + if ( conf == NULL ) + ast_log( LOG_ERROR, "unable to find or create requested conference\n" ) ; + } + else + { + // + // existing conference found, add new member to the conference + // + // once we call add_member(), this thread + // is responsible for calling delete_member() + // + add_member( member, conf ) ; + } + + // release the conference list lock + ast_mutex_unlock(&conflist_lock); + + return conf ; +} + +// This function should be called with conflist_lock mutex being held +struct ast_conference* find_conf( const char* name ) +{ + // no conferences exist + if ( conflist == NULL ) + { + ast_log( AST_CONF_DEBUG, "conflist has not yet been initialized, name => %s\n", name ) ; + return NULL ; + } + + struct ast_conference *conf = conflist ; + + // loop through conf list + while ( conf != NULL ) + { + if ( strncasecmp( (char*)&(conf->name), name, 80 ) == 0 ) + { + // found conf name match + ast_log( AST_CONF_DEBUG, "found conference in conflist, name => %s\n", name ) ; + return conf; + } + conf = conf->next ; + } + + ast_log( AST_CONF_DEBUG, "unable to find conference in conflist, name => %s\n", name ) ; + return NULL; +} + +// This function should be called with conflist_lock held +struct ast_conference* create_conf( char* name, struct ast_conf_member* member ) +{ + ast_log( AST_CONF_DEBUG, "entered create_conf, name => %s\n", name ) ; + + // + // allocate memory for conference + // + + struct ast_conference *conf = malloc( sizeof( struct ast_conference ) ) ; + + if ( conf == NULL ) + { + ast_log( LOG_ERROR, "unable to malloc ast_conference\n" ) ; + return NULL ; + } + + // + // initialize conference + // + + conf->next = NULL ; + conf->memberlist = NULL ; + + conf->membercount = 0 ; + conf->conference_thread = -1 ; + + conf->debug_flag = 0 ; + + conf->id_count = 0; + + conf->default_video_source_id = -1; + conf->current_video_source_id = -1; + //conf->current_video_source_timestamp = ast_tvnow(); + conf->video_locked = 0; + + // zero stats + memset( &conf->stats, 0x0, sizeof( ast_conference_stats ) ) ; + + // record start time + conf->stats.time_entered = ast_tvnow(); + + // copy name to conference + strncpy( (char*)&(conf->name), name, sizeof(conf->name) - 1 ) ; + strncpy( (char*)&(conf->stats.name), name, sizeof(conf->name) - 1 ) ; + + // initialize mutexes + ast_mutex_init( &conf->lock ) ; + + // build translation paths + conf->from_slinear_paths[ AC_SLINEAR_INDEX ] = NULL ; + conf->from_slinear_paths[ AC_ULAW_INDEX ] = ast_translator_build_path( AST_FORMAT_ULAW, AST_FORMAT_SLINEAR ) ; + conf->from_slinear_paths[ AC_ALAW_INDEX ] = ast_translator_build_path( AST_FORMAT_ALAW, AST_FORMAT_SLINEAR ) ; + conf->from_slinear_paths[ AC_GSM_INDEX ] = ast_translator_build_path( AST_FORMAT_GSM, AST_FORMAT_SLINEAR ) ; + conf->from_slinear_paths[ AC_SPEEX_INDEX ] = ast_translator_build_path( AST_FORMAT_SPEEX, AST_FORMAT_SLINEAR ) ; +#ifdef AC_USE_G729A + conf->from_slinear_paths[ AC_G729A_INDEX ] = ast_translator_build_path( AST_FORMAT_G729A, AST_FORMAT_SLINEAR ) ; +#endif + + // add the initial member + add_member( member, conf ) ; + + ast_log( AST_CONF_DEBUG, "added new conference to conflist, name => %s\n", name ) ; + + // + // spawn thread for new conference, using conference_exec( conf ) + // + // acquire conference mutexes + ast_mutex_lock( &conf->lock ) ; + + if ( ast_pthread_create( &conf->conference_thread, NULL, (void*)conference_exec, conf ) == 0 ) + { + // detach the thread so it doesn't leak + pthread_detach( conf->conference_thread ) ; + + // prepend new conference to conflist + conf->next = conflist ; + conflist = conf ; + + // release conference mutexes + ast_mutex_unlock( &conf->lock ) ; + + ast_log( AST_CONF_DEBUG, "started conference thread for conference, name => %s\n", conf->name ) ; + } + else + { + ast_log( LOG_ERROR, "unable to start conference thread for conference %s\n", conf->name ) ; + + conf->conference_thread = -1 ; + + // release conference mutexes + ast_mutex_unlock( &conf->lock ) ; + + // clean up conference + free( conf ) ; + conf = NULL ; + } + + // count new conference + if ( conf != NULL ) + ++conference_count ; + + return conf ; +} + +//This function should be called with conflist_lock and conf->lock held +void remove_conf( struct ast_conference *conf ) +{ + int c; + + // ast_log( AST_CONF_DEBUG, "attempting to remove conference, name => %s\n", conf->name ) ; + + struct ast_conference *conf_current = conflist ; + struct ast_conference *conf_temp = NULL ; + + // loop through list of conferences + while ( conf_current != NULL ) + { + // if conf_current point to the passed conf, + if ( conf_current == conf ) + { + if ( conf_temp == NULL ) + { + // this is the first conf in the list, so we just point + // conflist past the current conf to the next + conflist = conf_current->next ; + } + else + { + // this is not the first conf in the list, so we need to + // point the preceeding conf to the next conf in the list + conf_temp->next = conf_current->next ; + } + + // + // do some frame clean up + // + + for ( c = 0 ; c < AC_SUPPORTED_FORMATS ; ++c ) + { + // free the translation paths + if ( conf_current->from_slinear_paths[ c ] != NULL ) + { + ast_translator_free_path( conf_current->from_slinear_paths[ c ] ) ; + conf_current->from_slinear_paths[ c ] = NULL ; + } + } + + // calculate time in conference + // total time converted to seconds + long tt = ast_tvdiff_ms(ast_tvnow(), + conf_current->stats.time_entered) / 1000; + + // report accounting information + if (conf->debug_flag) + { + ast_log( LOG_NOTICE, "conference accounting, fi => %ld, fo => %ld, fm => %ld, tt => %ld\n", + conf_current->stats.frames_in, conf_current->stats.frames_out, conf_current->stats.frames_mixed, tt ) ; + + ast_log( AST_CONF_DEBUG, "removed conference, name => %s\n", conf_current->name ) ; + } + + ast_mutex_unlock( &conf_current->lock ) ; + + free( conf_current ) ; + conf_current = NULL ; + + break ; + } + + // save a refence to the soon to be previous conf + conf_temp = conf_current ; + + // move conf_current to the next in the list + conf_current = conf_current->next ; + } + + // count new conference + --conference_count ; + + return ; +} + +int get_new_id( struct ast_conference *conf ) +{ + // must have the conf lock when calling this + int newid; + struct ast_conf_member *othermember; + // get a video ID for this member + newid = 0; + othermember = conf->memberlist; + while (othermember) + { + if (othermember->id == newid) + { + newid++; + othermember = conf->memberlist; + } + else + { + othermember = othermember->next; + } + } + return newid; +} + + +int end_conference(const char *name, int hangup ) +{ + struct ast_conference *conf; + + // acquire the conference list lock + ast_mutex_lock(&conflist_lock); + + conf = find_conf(name); + if ( conf == NULL ) + { + ast_log( LOG_WARNING, "could not find conference\n" ) ; + + // release the conference list lock + ast_mutex_unlock(&conflist_lock); + + return -1 ; + } + + // acquire the conference lock + ast_mutex_lock( &conf->lock ) ; + + // get list of conference members + struct ast_conf_member* member = conf->memberlist ; + + // loop over member list and request hangup + while ( member != NULL ) + { + // acquire member mutex and request hangup + // or just kick + ast_mutex_lock( &member->lock ) ; + if (hangup) + ast_softhangup( member->chan, 1 ) ; + else + member->kick_flag = 1; + ast_mutex_unlock( &member->lock ) ; + + // go on to the next member + // ( we have the conf lock, so we know this is okay ) + member = member->next ; + } + + // release the conference lock + ast_mutex_unlock( &conf->lock ) ; + + // release the conference list lock + ast_mutex_unlock(&conflist_lock); + + return 0 ; +} + +// +// member-related functions +// + +// This function should be called with conflist_lock held +void add_member( struct ast_conf_member *member, struct ast_conference *conf ) +{ + int newid, last_id; + struct ast_conf_member *othermember; + int count; + + if ( conf == NULL ) + { + ast_log( LOG_ERROR, "unable to add member to NULL conference\n" ) ; + return ; + } + + // acquire the conference lock + ast_mutex_lock( &conf->lock ) ; + + if (member->id < 0) + { + // get an ID for this member + newid = get_new_id( conf ); + member->id = newid; + } else + { + // boot anyone who has this id already + othermember = conf->memberlist; + while (othermember) + { + if (othermember->id == member->id) + othermember->id = -1; + othermember = othermember->next; + } + } + + if ( member->mute_video ) + { + send_text_message_to_member(member, AST_CONF_CONTROL_STOP_VIDEO); + } + + // set a long term id + int new_initial_id = 0; + othermember = conf->memberlist; + while (othermember) + { + if (othermember->initial_id >= new_initial_id) + new_initial_id++; + + othermember = othermember->next; + } + member->initial_id = new_initial_id; + + + ast_log( AST_CONF_DEBUG, "new video id %d\n", newid) ; + + if (conf->memberlist) last_id = conf->memberlist->id; + else last_id = 0; + + if (member->req_id < 0) // otherwise pre-selected in create_member + { + // want to watch the last person to 0 or 1 (for now) + if (member->id > 0) member->req_id = 0; + else member->req_id = 1; + } + + member->next = conf->memberlist ; // next is now list + conf->memberlist = member ; // member is now at head of list + + // update conference stats + count = count_member( member, conf, 1 ) ; + + ast_log( AST_CONF_DEBUG, "member added to conference, name => %s\n", conf->name ) ; + + // release the conference lock + ast_mutex_unlock( &conf->lock ) ; + + return ; +} + +int remove_member( struct ast_conf_member* member, struct ast_conference* conf ) +{ + // check for member + if ( member == NULL ) + { + ast_log( LOG_WARNING, "unable to remove NULL member\n" ) ; + return -1 ; + } + + // check for conference + if ( conf == NULL ) + { + ast_log( LOG_WARNING, "unable to remove member from NULL conference\n" ) ; + return -1 ; + } + + // + // loop through the member list looking + // for the requested member + // + + ast_mutex_lock( &conf->lock ); + + struct ast_conf_member *member_list = conf->memberlist ; + struct ast_conf_member *member_temp = NULL ; + + int count = -1 ; // default return code + + while ( member_list != NULL ) + { + // set conference to send no_video to anyone who was watching us + ast_mutex_lock( &member_list->lock ) ; + if (member_list->req_id == member->id) + { + member_list->conference = 1; + } + ast_mutex_unlock( &member_list->lock ) ; + member_list = member_list->next ; + } + + member_list = conf->memberlist ; + + int member_is_moderator = member->ismoderator; + + while ( member_list != NULL ) + { + // If member is driven by the currently visited member, break the association + if ( member_list->driven_member == member ) + { + // Acquire member mutex + ast_mutex_lock(&member_list->lock); + + member_list->driven_member = NULL; + + // Release member mutex + ast_mutex_unlock(&member_list->lock); + } + + if ( member_list == member ) + { + + // + // log some accounting information + // + + // calculate time in conference (in seconds) + long tt = ast_tvdiff_ms(ast_tvnow(), + member->time_entered) / 1000; + + if (conf->debug_flag) + { + ast_log( + LOG_NOTICE, + "member accounting, channel => %s, te => %ld, fi => %ld, fid => %ld, fo => %ld, fod => %ld, tt => %ld\n", + member->channel_name, + member->time_entered.tv_sec, member->frames_in, member->frames_in_dropped, + member->frames_out, member->frames_out_dropped, tt + ) ; + } + + // + // if this is the first member in the linked-list, + // skip over the first member in the list, else + // + // point the previous 'next' to the current 'next', + // thus skipping the current member in the list + // + if ( member_temp == NULL ) + conf->memberlist = member->next ; + else + member_temp->next = member->next ; + + // update conference stats + count = count_member( member, conf, 0 ) ; + + // Check if member is the default or current video source + if ( conf->current_video_source_id == member->id ) + { + if ( conf->video_locked ) + unlock_conference(conf->name); + do_video_switching(conf, conf->default_video_source_id, 0); + } else if ( conf->default_video_source_id == member->id ) + { + conf->default_video_source_id = -1; + } + + // output to manager... + manager_event( + EVENT_FLAG_CALL, + "ConferenceLeave", + "ConferenceName: %s\r\n" + "Member: %d\r\n" + "Channel: %s\r\n" + "CallerID: %s\r\n" + "CallerIDName: %s\r\n" + "Duration: %ld\r\n" + "Count: %d\r\n", + conf->name, + member->id, + member->channel_name, + member->callerid, + member->callername, + tt, count + ) ; + + // save a pointer to the current member, + // and then point to the next member in the list + member_list = member_list->next ; + + // leave member_temp alone. + // it already points to the previous (or NULL). + // it will still be the previous after member is deleted + + // notify others about leave + char * leave_snd = member->leave_snd; + if (conf->membercount && strcmp(leave_snd, "-")>1) + { + struct ast_conf_member *membertest = conf->memberlist; + while (membertest != NULL) + { + if (membertest != member) + { + // lock member for basic_play_sound + ast_mutex_lock(&membertest->lock); + + // basic_play_sound unlock member automatically. do not mute on enter message + if (!basic_play_sound (membertest, leave_snd, 0)) + { + ast_log(LOG_ERROR, "playing conference[%d] leave message <%s> FAILED on <%s>\n", conf->membercount, leave_snd, membertest->channel_name) ; + } + else + { + ast_log(LOG_NOTICE, "playing conference[%d] leave message <%s> on <%s>\n", conf->membercount, leave_snd, membertest->channel_name); + } + membertest = membertest->next; + } + } + } + + // delete the member + delete_member( member ) ; + + ast_log( AST_CONF_DEBUG, "removed member from conference, name => %s, remaining => %d\n", + conf->name, conf->membercount ) ; + + //break ; + } + else + { + // if member is a moderator, we end the conference when they leave + if ( member_is_moderator ) + { + ast_mutex_lock( &member_list->lock ) ; + member_list->kick_flag = 1; + ast_mutex_unlock( &member_list->lock ) ; + } + + // save a pointer to the current member, + // and then point to the next member in the list + member_temp = member_list ; + member_list = member_list->next ; + } + } + ast_mutex_unlock( &conf->lock ); + + // return -1 on error, or the number of members + // remaining if the requested member was deleted + return count ; +} + +int count_member( struct ast_conf_member* member, struct ast_conference* conf, short add_member ) +{ + if ( member == NULL || conf == NULL ) + { + ast_log( LOG_WARNING, "unable to count member\n" ) ; + return -1 ; + } + + short delta = ( add_member == 1 ) ? 1 : -1 ; + + // increment member count + conf->membercount += delta ; + + return conf->membercount ; +} + +// +// queue incoming frame functions +// + + + + +// +// get conference stats +// + +// +// returns: -1 => error, 0 => debugging off, 1 => debugging on +// state: on => 1, off => 0, toggle => -1 +// +int set_conference_debugging( const char* name, int state ) +{ + if ( name == NULL ) + return -1 ; + + // acquire mutex + ast_mutex_lock( &conflist_lock ) ; + + struct ast_conference *conf = conflist ; + int new_state = -1 ; + + // loop through conf list + while ( conf != NULL ) + { + if ( strncasecmp( (const char*)&(conf->name), name, 80 ) == 0 ) + { + // lock conference + // ast_mutex_lock( &(conf->lock) ) ; + + // toggle or set the state + if ( state == -1 ) + conf->debug_flag = ( conf->debug_flag == 0 ) ? 1 : 0 ; + else + conf->debug_flag = ( state == 0 ) ? 0 : 1 ; + + new_state = conf->debug_flag ; + + // unlock conference + // ast_mutex_unlock( &(conf->lock) ) ; + + break ; + } + + conf = conf->next ; + } + + // release mutex + ast_mutex_unlock( &conflist_lock ) ; + + return new_state ; +} + + +int get_conference_count( void ) +{ + return conference_count ; +} + +int show_conference_stats ( int fd ) +{ + // no conferences exist + if ( conflist == NULL ) + { + ast_log( AST_CONF_DEBUG, "conflist has not yet been initialized.\n") ; + return 0 ; + } + + // acquire mutex + ast_mutex_lock( &conflist_lock ) ; + + struct ast_conference *conf = conflist ; + + ast_cli( fd, "%-20.20s %-40.40s\n", "Name", "Members") ; + + // loop through conf list + while ( conf != NULL ) + { + ast_cli( fd, "%-20.20s %3d\n", conf->name, conf->membercount ) ; + conf = conf->next ; + } + + // release mutex + ast_mutex_unlock( &conflist_lock ) ; + + return 1 ; +} + +int show_conference_list ( int fd, const char *name ) +{ + struct ast_conf_member *member; + + // no conferences exist + if ( conflist == NULL ) + { + ast_log( AST_CONF_DEBUG, "conflist has not yet been initialized, name => %s\n", name ) ; + return 0 ; + } + + // acquire mutex + ast_mutex_lock( &conflist_lock ) ; + + struct ast_conference *conf = conflist ; + + // loop through conf list + while ( conf != NULL ) + { + if ( strncasecmp( (const char*)&(conf->name), name, 80 ) == 0 ) + { + // acquire conference mutex + ast_mutex_lock(&conf->lock); + + // do the biz + member = conf->memberlist ; + while ( member != NULL ) + { + ast_cli( fd, "User #: %d ", member->id ) ; + ast_cli( fd, "Channel: %s ", member->channel_name ) ; + + ast_cli( fd, "Flags:"); + if ( member->mute_video ) ast_cli( fd, "C"); + if ( member->norecv_video ) ast_cli( fd, "c"); + if ( member->mute_audio ) ast_cli( fd, "L"); + if ( member->norecv_audio ) ast_cli( fd, "l"); + if ( member->vad_flag ) ast_cli( fd, "V"); + if ( member->denoise_flag ) ast_cli( fd, "D"); + if ( member->agc_flag ) ast_cli( fd, "A"); + if ( member->dtmf_switch ) ast_cli( fd, "X"); + if ( member->dtmf_relay ) ast_cli( fd, "R"); + if ( member->vad_switch ) ast_cli( fd, "S"); + if ( member->ismoderator ) ast_cli( fd, "M"); + if ( member->no_camera ) ast_cli( fd, "N"); + if ( member->does_text ) ast_cli( fd, "t"); + if ( member->via_telephone ) ast_cli( fd, "T"); + ast_cli( fd, " " ); + + if ( member->id == conf->default_video_source_id ) + ast_cli(fd, "Default "); + if ( member->id == conf->current_video_source_id ) + { + ast_cli(fd, "Showing "); + if ( conf->video_locked ) + ast_cli(fd, "Locked "); + } + if ( member->driven_member != NULL ) + { + ast_cli(fd, "Driving:%s(%d) ", member->driven_member->channel_name, member->driven_member->id); + } + + ast_cli( fd, "\n"); + member = member->next; + } + + // release conference mutex + ast_mutex_unlock(&conf->lock); + + break ; + } + + conf = conf->next ; + } + + // release mutex + ast_mutex_unlock( &conflist_lock ) ; + + return 1 ; +} + +/* Dump list of conference info */ +int manager_conference_list( struct mansession *s, const struct message *m ) +{ + const char *id = astman_get_header(m,"ActionID"); + const char *conffilter = astman_get_header(m,"Conference"); + char idText[256] = ""; + struct ast_conf_member *member; + + astman_send_ack(s, m, "Conference list will follow"); + + // no conferences exist + if ( conflist == NULL ) + ast_log( AST_CONF_DEBUG, "conflist has not yet been initialized, name => %s\n", conffilter );; + + if (!ast_strlen_zero(id)) { + snprintf(idText,256,"ActionID: %s\r\n",id); + } + + // acquire mutex + ast_mutex_lock( &conflist_lock ) ; + + struct ast_conference *conf = conflist ; + + // loop through conf list + while ( conf != NULL ) + { + if ( strncasecmp( (const char*)&(conf->name), conffilter, 80 ) == 0 ) + { + // do the biz + member = conf->memberlist ; + while (member != NULL) + { + astman_append(s, "Event: ConferenceEntry\r\n" + "ConferenceName: %s\r\n" + "Member: %d\r\n" + "Channel: %s\r\n" + "CallerID: %s\r\n" + "CallerIDName: %s\r\n" + "Muted: %s\r\n" + "VideoMuted: %s\r\n" + "Default: %s\r\n" + "Current: %s\r\n" + "%s" + "\r\n", + conf->name, + member->id, + member->channel_name, + member->chan->cid.cid_num ? member->chan->cid.cid_num : "unknown", + member->chan->cid.cid_name ? member->chan->cid.cid_name : "unknown", + member->mute_audio ? "YES" : "NO", + member->mute_video ? "YES" : "NO", + member->id == conf->default_video_source_id ? "YES" : "NO", + member->id == conf->current_video_source_id ? "YES" : "NO", + idText); + member = member->next; + } + break ; + } + + conf = conf->next ; + } + + // release mutex + ast_mutex_unlock( &conflist_lock ) ; + + astman_append(s, + "Event: ConferenceListComplete\r\n" + "%s" + "\r\n",idText); + + return RESULT_SUCCESS; +} + +int kick_member ( const char* confname, int user_id) +{ + struct ast_conf_member *member; + int res = 0; + + // no conferences exist + if ( conflist == NULL ) + { + ast_log( AST_CONF_DEBUG, "conflist has not yet been initialized, name => %s\n", confname ) ; + return 0 ; + } + + // acquire mutex + ast_mutex_lock( &conflist_lock ) ; + + struct ast_conference *conf = conflist ; + + // loop through conf list + while ( conf != NULL ) + { + if ( strncasecmp( (const char*)&(conf->name), confname, 80 ) == 0 ) + { + // do the biz + ast_mutex_lock( &conf->lock ) ; + member = conf->memberlist ; + while (member != NULL) + { + if (member->id == user_id) + { + ast_mutex_lock( &member->lock ) ; + member->kick_flag = 1; + //ast_soft_hangup(member->chan); + ast_mutex_unlock( &member->lock ) ; + + res = 1; + } + member = member->next; + } + ast_mutex_unlock( &conf->lock ) ; + break ; + } + + conf = conf->next ; + } + + // release mutex + ast_mutex_unlock( &conflist_lock ) ; + + return res ; +} + +int kick_channel ( const char *confname, const char *channel) +{ + struct ast_conf_member *member; + int res = 0; + + // no conferences exist + if ( conflist == NULL ) + { + ast_log( AST_CONF_DEBUG, "conflist has not yet been initialized, name => %s\n", confname ) ; + return 0 ; + } + + if ( confname == NULL || channel == NULL || strlen(confname) == 0 || strlen(channel) == 0 ) + return 0; + + // acquire mutex + ast_mutex_lock( &conflist_lock ) ; + + struct ast_conference *conf = conflist ; + + // loop through conf list + while ( conf != NULL ) + { + if ( strncasecmp( (const char*)&(conf->name), confname, 80 ) == 0 ) + { + // do the biz + ast_mutex_lock( &conf->lock ) ; + member = conf->memberlist ; + while ( member != NULL ) + { + if ( strncasecmp( member->channel_name, channel, 80 ) == 0 ) + { + ast_mutex_lock( &member->lock ) ; + member->kick_flag = 1; + //ast_soft_hangup(member->chan); + ast_mutex_unlock( &member->lock ) ; + + res = 1; + } + member = member->next; + } + ast_mutex_unlock( &conf->lock ) ; + break ; + } + + conf = conf->next ; + } + + // release mutex + ast_mutex_unlock( &conflist_lock ) ; + + return res ; +} + +int kick_all ( void ) +{ + struct ast_conf_member *member; + int res = 0; + + // no conferences exist + if ( conflist == NULL ) + { + ast_log( AST_CONF_DEBUG, "conflist has not yet been initialized\n" ) ; + return 0 ; + } + + // acquire mutex + ast_mutex_lock( &conflist_lock ) ; + + struct ast_conference *conf = conflist ; + + // loop through conf list + while ( conf != NULL ) + { + // do the biz + ast_mutex_lock( &conf->lock ) ; + member = conf->memberlist ; + while (member != NULL) + { + ast_mutex_lock( &member->lock ) ; + member->kick_flag = 1; + ast_mutex_unlock( &member->lock ) ; + member = member->next; + } + ast_mutex_unlock( &conf->lock ) ; + break ; + + conf = conf->next ; + } + + // release mutex + ast_mutex_unlock( &conflist_lock ) ; + + return res ; +} + +int mute_member ( const char* confname, int user_id) +{ + struct ast_conf_member *member; + int res = 0; + + // no conferences exist + if ( conflist == NULL ) + { + ast_log( AST_CONF_DEBUG, "conflist has not yet been initialized, name => %s\n", confname ) ; + return 0 ; + } + + // acquire mutex + ast_mutex_lock( &conflist_lock ) ; + + struct ast_conference *conf = conflist ; + + // loop through conf list + while ( conf != NULL ) + { + if ( strncasecmp( (const char*)&(conf->name), confname, 80 ) == 0 ) + { + // do the biz + ast_mutex_lock( &conf->lock ) ; + member = conf->memberlist ; + while (member != NULL) + { + if (member->id == user_id) + { + ast_mutex_lock( &member->lock ) ; + member->mute_audio = 1; + ast_mutex_unlock( &member->lock ) ; + res = 1; + } + member = member->next; + } + ast_mutex_unlock( &conf->lock ) ; + break ; + } + + conf = conf->next ; + } + + // release mutex + ast_mutex_unlock( &conflist_lock ) ; + + return res ; +} + +int mute_channel ( const char* confname, const char* user_chan) +{ + struct ast_conf_member *member; + int res = 0; + + // no conferences exist + if ( conflist == NULL ) + { + ast_log( AST_CONF_DEBUG, "conflist has not yet been initialized, name => %s\n", confname ) ; + return 0 ; + } + + // acquire mutex + ast_mutex_lock( &conflist_lock ) ; + + struct ast_conference *conf = conflist ; + + // loop through conf list + while ( conf != NULL ) + { + if ( strncasecmp( (const char*)&(conf->name), confname, 80 ) == 0 ) + { + // do the biz + ast_mutex_lock( &conf->lock ) ; + member = conf->memberlist ; + while (member != NULL) + { + if (strncasecmp( member->channel_name, user_chan, 80 ) == 0) + { + ast_mutex_lock( &member->lock ) ; + member->mute_audio = 1; + ast_mutex_unlock( &member->lock ) ; + res = 1; + } + member = member->next; + } + ast_mutex_unlock( &conf->lock ) ; + break ; + } + + conf = conf->next ; + } + + // release mutex + ast_mutex_unlock( &conflist_lock ) ; + + return res ; +} + +int unmute_member ( const char* confname, int user_id) +{ + struct ast_conf_member *member; + int res = 0; + + // no conferences exist + if ( conflist == NULL ) + { + ast_log( AST_CONF_DEBUG, "conflist has not yet been initialized, name => %s\n", confname ) ; + return 0 ; + } + + // acquire mutex + ast_mutex_lock( &conflist_lock ) ; + + struct ast_conference *conf = conflist ; + + // loop through conf list + while ( conf != NULL ) + { + if ( strncasecmp( (const char*)&(conf->name), confname, 80 ) == 0 ) + { + // do the biz + ast_mutex_lock( &conf->lock ) ; + member = conf->memberlist ; + while (member != NULL) + { + if (member->id == user_id) + { + ast_mutex_lock( &member->lock ) ; + member->mute_audio = 0; + ast_mutex_unlock( &member->lock ) ; + res = 1; + } + member = member->next; + } + ast_mutex_unlock( &conf->lock ) ; + break ; + } + + conf = conf->next ; + } + + // release mutex + ast_mutex_unlock( &conflist_lock ) ; + + return res ; +} + +int unmute_channel (const char* confname, const char* user_chan) +{ + struct ast_conf_member *member; + int res = 0; + + // no conferences exist + if ( conflist == NULL ) + { + ast_log( AST_CONF_DEBUG, "conflist has not yet been initialized, name => %s\n", confname ) ; + return 0 ; + } + + // acquire mutex + ast_mutex_lock( &conflist_lock ) ; + + struct ast_conference *conf = conflist ; + + // loop through conf list + while ( conf != NULL ) + { + if ( strncasecmp( (const char*)&(conf->name), confname, 80 ) == 0 ) + { + // do the biz + ast_mutex_lock( &conf->lock ) ; + member = conf->memberlist ; + while (member != NULL) + { + if (strncasecmp( member->channel_name, user_chan, 80 ) == 0) + { + ast_mutex_lock( &member->lock ) ; + member->mute_audio = 0; + ast_mutex_unlock( &member->lock ) ; + res = 1; + } + member = member->next; + } + ast_mutex_unlock( &conf->lock ) ; + break ; + } + + conf = conf->next ; + } + + // release mutex + ast_mutex_unlock( &conflist_lock ) ; + + return res ; +} + +int viewstream_switch ( const char* confname, int user_id, int stream_id ) +{ + struct ast_conf_member *member; + int res = 0; + + // no conferences exist + if ( conflist == NULL ) + { + ast_log( AST_CONF_DEBUG, "conflist has not yet been initialized, name => %s\n", confname ) ; + return 0 ; + } + + // acquire mutex + ast_mutex_lock( &conflist_lock ) ; + + struct ast_conference *conf = conflist ; + + // loop through conf list + while ( conf != NULL ) + { + if ( strncasecmp( (const char*)&(conf->name), confname, 80 ) == 0 ) + { + // do the biz + ast_mutex_lock( &conf->lock ) ; + member = conf->memberlist ; + while (member != NULL) + { + if (member->id == user_id) + { + // switch the video + ast_mutex_lock( &member->lock ) ; + + member->req_id = stream_id; + member->conference = 1; + + ast_mutex_unlock( &member->lock ) ; + res = 1; + } + member = member->next; + } + ast_mutex_unlock( &conf->lock ) ; + break ; + } + + conf = conf->next ; + } + + // release mutex + ast_mutex_unlock( &conflist_lock ) ; + + return res ; +} + +int viewchannel_switch ( const char* confname, const char* userchan, const char* streamchan ) +{ + struct ast_conf_member *member; + int res = 0; + int stream_id = -1; + + // no conferences exist + if ( conflist == NULL ) + { + ast_log( AST_CONF_DEBUG, "conflist has not yet been initialized, name => %s\n", confname ) ; + return 0 ; + } + + // acquire mutex + ast_mutex_lock( &conflist_lock ) ; + + struct ast_conference *conf = conflist ; + + // loop through conf list + while ( conf != NULL ) + { + if ( strncasecmp( (const char*)&(conf->name), confname, 80 ) == 0 ) + { + // do the biz + ast_mutex_lock( &conf->lock ) ; + member = conf->memberlist ; + while (member != NULL) + { + if (strncasecmp( member->channel_name, streamchan, 80 ) == 0) + { + stream_id = member->id; + } + member = member->next; + } + ast_mutex_unlock( &conf->lock ) ; + if (stream_id >= 0) + { + // do the biz + ast_mutex_lock( &conf->lock ) ; + member = conf->memberlist ; + while (member != NULL) + { + if (strncasecmp( member->channel_name, userchan, 80 ) == 0) + { + // switch the video + ast_mutex_lock( &member->lock ) ; + + member->req_id = stream_id; + member->conference = 1; + + ast_mutex_unlock( &member->lock ) ; + res = 1; + } + member = member->next; + } + ast_mutex_unlock( &conf->lock ) ; + } + break ; + } + + conf = conf->next ; + } + + // release mutex + ast_mutex_unlock( &conflist_lock ) ; + + return res ; +} + +int get_conference_stats( ast_conference_stats* stats, int requested ) +{ + // no conferences exist + if ( conflist == NULL ) + { + ast_log( AST_CONF_DEBUG, "conflist has not yet been initialize\n" ) ; + return 0 ; + } + + // acquire mutex + ast_mutex_lock( &conflist_lock ) ; + + // compare the number of requested to the number of available conferences + requested = ( get_conference_count() < requested ) ? get_conference_count() : requested ; + + // + // loop through conf list + // + + struct ast_conference* conf = conflist ; + int count = 0 ; + + while ( count <= requested && conf != NULL ) + { + // copy stats struct to array + stats[ count ] = conf->stats ; + + conf = conf->next ; + ++count ; + } + + // release mutex + ast_mutex_unlock( &conflist_lock ) ; + + return count ; +} + +int get_conference_stats_by_name( ast_conference_stats* stats, const char* name ) +{ + // no conferences exist + if ( conflist == NULL ) + { + ast_log( AST_CONF_DEBUG, "conflist has not yet been initialized, name => %s\n", name ) ; + return 0 ; + } + + // make sure stats is null + stats = NULL ; + + // acquire mutex + ast_mutex_lock( &conflist_lock ) ; + + struct ast_conference *conf = conflist ; + + // loop through conf list + while ( conf != NULL ) + { + if ( strncasecmp( (const char*)&(conf->name), name, 80 ) == 0 ) + { + // copy stats for found conference + *stats = conf->stats ; + break ; + } + + conf = conf->next ; + } + + // release mutex + ast_mutex_unlock( &conflist_lock ) ; + + return ( stats == NULL ) ? 0 : 1 ; +} + +struct ast_conf_member *find_member (const char *chan, int lock) +{ + struct ast_conf_member *found = NULL; + struct ast_conf_member *member; + struct ast_conference *conf; + + ast_mutex_lock( &conflist_lock ) ; + + conf = conflist; + + // loop through conf list + while ( conf != NULL && !found ) + { + // lock conference + ast_mutex_lock( &conf->lock ); + + member = conf->memberlist ; + + while (member != NULL) + { + if(!strcmp(member->channel_name, chan)) { + found = member; + if(lock) + ast_mutex_lock(&member->lock); + break; + } + member = member->next; + } + + // unlock conference + ast_mutex_unlock( &conf->lock ); + + conf = conf->next ; + } + + // release mutex + ast_mutex_unlock( &conflist_lock ) ; + + return found; +} + +// All the VAD-based video switching magic happens here +// This function should be called inside conference_exec +// The conference mutex should be locked, we don't have to do it here +void do_VAD_switching(struct ast_conference *conf) +{ + struct ast_conf_member *member; + struct timeval current_time; + long longest_speaking; + struct ast_conf_member *longest_speaking_member; + int current_silent, current_no_camera, current_video_mute; + int default_no_camera, default_video_mute; + + current_time = ast_tvnow(); + + // Scan the member list looking for the longest speaking member + // We also check if the currently speaking member has been silent for a while + // Also, we check for camera disabled or video muted members + // We say that a member is speaking after his speaking state has been on for + // at least AST_CONF_VIDEO_START_TIMEOUT ms + // We say that a member is silent after his speaking state has been off for + // at least AST_CONF_VIDEO_STOP_TIMEOUT ms + longest_speaking = 0; + longest_speaking_member = NULL; + current_silent = 0; + current_no_camera = 0; + current_video_mute = 0; + default_no_camera = 0; + default_video_mute = 0; + for ( member = conf->memberlist ; + member != NULL ; + member = member->next ) + { + // Has the state changed since last time through this loop? Notify! + if ( member->speaking_state_notify ) + { +/* fprintf(stderr, "Mihai: member %d, channel %s has changed state to %s\n", + member->id, + member->channel_name, + ((member->speaking_state == 1 ) ? "speaking" : "silent") + ); */ + } + + // If a member connects via telephone, they don't have video + if ( member->via_telephone ) + continue; + + // We check for no VAD switching, video-muted or camera disabled + // If yes, this member will not be considered as a candidate for switching + // If this is the currently speaking member, then mark it so we force a switch + if ( !member->vad_switch ) + continue; + + if ( member->mute_video ) + { + if ( member->id == conf->default_video_source_id ) + default_video_mute = 1; + if ( member->id == conf->current_video_source_id ) + current_video_mute = 1; + else + continue; + } + + if ( member->no_camera ) + { + if ( member->id == conf->default_video_source_id ) + default_no_camera = 1; + if ( member->id == conf->current_video_source_id ) + current_no_camera = 1; + else + continue; + } + + // Check if current speaker has been silent for a while + if ( member->id == conf->current_video_source_id && + member->speaking_state == 0 && + ast_tvdiff_ms(current_time, member->last_state_change) > AST_CONF_VIDEO_STOP_TIMEOUT ) + { + current_silent = 1; + } + + // Find a candidate to switch to by looking for the longest speaking member + // We exclude the current video source from the search + if ( member->id != conf->current_video_source_id && member->speaking_state == 1 ) + { + long tmp = ast_tvdiff_ms(current_time, member->last_state_change); + if ( tmp > AST_CONF_VIDEO_START_TIMEOUT && tmp > longest_speaking ) + { + longest_speaking = tmp; + longest_speaking_member = member; + } + } + } + + // We got our results, now let's make a decision + // If the currently speaking member has been marked as silent, then we take the longest + // speaking member. If no member is speaking, we go to default + // As a policy we don't want to switch away from a member that is speaking + // however, we might need to refine this to avoid a situation when a member has a + // low noise threshold or its VAD is simply stuck + if ( current_silent || current_no_camera || current_video_mute || conf->current_video_source_id < 0 ) + { + if ( longest_speaking_member != NULL ) + { + do_video_switching(conf, longest_speaking_member->id, 0); + } else + { + // If there's nobody speaking and we have a default that can send video, switch to it + // If not, then switch to empty (-1) + if ( conf->default_video_source_id >= 0 && + !default_no_camera && + !default_video_mute + ) + do_video_switching(conf, conf->default_video_source_id, 0); + else + do_video_switching(conf, -1, 0); + } + } +} + +int lock_conference(const char *conference, int member_id) +{ + struct ast_conference *conf; + struct ast_conf_member *member; + int res; + + if ( conference == NULL || member_id < 0 ) + return -1 ; + + // acquire mutex + ast_mutex_lock( &conflist_lock ) ; + + // Look for conference + res = 0; + for ( conf = conflist ; conf != NULL ; conf = conf->next ) + { + if ( strcmp(conference, conf->name) == 0 ) + { + // Search member list for our member + // acquire conference mutex + ast_mutex_lock( &conf->lock ); + + for ( member = conf->memberlist ; member != NULL ; member = member->next ) + { + if ( member->id == member_id && !member->mute_video ) + { + do_video_switching(conf, member_id, 0); + conf->video_locked = 1; + res = 1; + + manager_event(EVENT_FLAG_CALL, "ConferenceLock", "ConferenceName: %s\r\nChannel: %s\r\n", conf->name, member->channel_name); + break; + } + } + + // Release conference mutex + ast_mutex_unlock( &conf->lock ); + break; + } + } + + // release mutex + ast_mutex_unlock( &conflist_lock ) ; + + return res; +} + +int lock_conference_channel(const char *conference, const char *channel) +{ + struct ast_conference *conf; + struct ast_conf_member *member; + int res; + + if ( conference == NULL || channel == NULL ) + return -1 ; + + // acquire mutex + ast_mutex_lock( &conflist_lock ) ; + + // Look for conference + res = 0; + for ( conf = conflist ; conf != NULL ; conf = conf->next ) + { + if ( strcmp(conference, conf->name) == 0 ) + { + // Search member list for our member + // acquire conference mutex + ast_mutex_lock( &conf->lock ); + + for ( member = conf->memberlist ; member != NULL ; member = member->next ) + { + if ( strcmp(channel, member->channel_name) == 0 && !member->mute_video ) + { + do_video_switching(conf, member->id, 0); + conf->video_locked = 1; + res = 1; + + manager_event(EVENT_FLAG_CALL, "ConferenceLock", "ConferenceName: %s\r\nChannel: %s\r\n", conf->name, member->channel_name); + break; + } + } + + // Release conference mutex + ast_mutex_unlock( &conf->lock ); + break; + } + } + + // release mutex + ast_mutex_unlock( &conflist_lock ) ; + + return res; +} + +int unlock_conference(const char *conference) +{ + struct ast_conference *conf; + int res; + + if ( conference == NULL ) + return -1; + + // acquire conference list mutex + ast_mutex_lock( &conflist_lock ) ; + + // Look for conference + res = 0; + for ( conf = conflist ; conf != NULL ; conf = conf->next ) + { + if ( strcmp(conference, conf->name) == 0 ) + { + conf->video_locked = 0; + manager_event(EVENT_FLAG_CALL, "ConferenceUnlock", "ConferenceName: %s\r\n", conf->name); + do_video_switching(conf, conf->default_video_source_id, 0); + res = 1; + + break; + } + } + + // release conference list mutex + ast_mutex_unlock( &conflist_lock ) ; + + return res; +} + +int set_default_id(const char *conference, int member_id) +{ + struct ast_conference *conf; + struct ast_conf_member *member; + int res; + + if ( conference == NULL ) + return -1 ; + + // acquire conference list mutex + ast_mutex_lock( &conflist_lock ) ; + + // Look for conference + res = 0; + for ( conf = conflist ; conf != NULL ; conf = conf->next ) + { + if ( strcmp(conference, conf->name) == 0 ) + { + if ( member_id < 0 ) + { + conf->default_video_source_id = -1; + manager_event(EVENT_FLAG_CALL, "ConferenceDefault", "ConferenceName: %s\r\nChannel: empty\r\n", conf->name); + res = 1; + break; + } else + { + // Search member list for our member + // acquire conference mutex + ast_mutex_lock( &conf->lock ); + + for ( member = conf->memberlist ; member != NULL ; member = member->next ) + { + // We do not allow video muted members or members that do not support + // VAD switching to become defaults + if ( member->id == member_id && + !member->mute_video && + member->vad_switch + ) + { + conf->default_video_source_id = member_id; + res = 1; + + manager_event(EVENT_FLAG_CALL, "ConferenceDefault", "ConferenceName: %s\r\nChannel: %s\r\n", conf->name, member->channel_name); + break; + } + } + + // Release conference mutex + ast_mutex_unlock( &conf->lock ); + break; + } + } + } + + // release conference list mutex + ast_mutex_unlock( &conflist_lock ) ; + + return res; + +} + +int set_default_channel(const char *conference, const char *channel) +{ + struct ast_conference *conf; + struct ast_conf_member *member; + int res; + + if ( conference == NULL || channel == NULL ) + return -1 ; + + // acquire conference list mutex + ast_mutex_lock( &conflist_lock ) ; + + // Look for conference + res = 0; + for ( conf = conflist ; conf != NULL ; conf = conf->next ) + { + if ( strcmp(conference, conf->name) == 0 ) + { + // Search member list for our member + // acquire conference mutex + ast_mutex_lock( &conf->lock ); + + for ( member = conf->memberlist ; member != NULL ; member = member->next ) + { + // We do not allow video muted members or members that do not support + // VAD switching to become defaults + if ( strcmp(channel, member->channel_name) == 0 && + !member->mute_video && + member->vad_switch + ) + { + conf->default_video_source_id = member->id; + res = 1; + + manager_event(EVENT_FLAG_CALL, "ConferenceDefault", "ConferenceName: %s\r\nChannel: %s\r\n", conf->name, member->channel_name); + break; + } + } + + // Release conference mutex + ast_mutex_unlock( &conf->lock ); + break; + } + } + + // release conference list mutex + ast_mutex_unlock( &conflist_lock ) ; + + return res; +} + +int video_mute_member(const char *conference, int member_id) +{ + struct ast_conference *conf; + struct ast_conf_member *member; + int res; + + if ( conference == NULL || member_id < 0 ) + return -1; + + res = 0; + + // acquire conference list mutex + ast_mutex_lock( &conflist_lock ) ; + + for ( conf = conflist ; conf != NULL ; conf = conf->next ) + { + if ( strcmp(conference, conf->name) == 0 ) + { + // acquire conference mutex + ast_mutex_lock( &conf->lock ); + + for ( member = conf->memberlist ; member != NULL ; member = member->next ) + { + if ( member->id == member_id ) + { + // acquire member mutex + ast_mutex_lock( &member->lock ); + + member->mute_video = 1; + + // release member mutex + ast_mutex_unlock( &member->lock ); + + manager_event(EVENT_FLAG_CALL, "ConferenceVideoMute", "ConferenceName: %s\r\nChannel: %s\r\n", conf->name, member->channel_name); + + if ( member->id == conf->current_video_source_id ) + { + do_video_switching(conf, conf->default_video_source_id, 0); + } + + res = 1; + break; + } + } + + // release conference mutex + ast_mutex_unlock( &conf->lock ); + + break; + } + } + + // release conference list mutex + ast_mutex_unlock( &conflist_lock ) ; + + return res; +} + +int video_unmute_member(const char *conference, int member_id) +{ + struct ast_conference *conf; + struct ast_conf_member *member; + int res; + + if ( conference == NULL || member_id < 0 ) + return -1; + + res = 0; + + // acquire conference list mutex + ast_mutex_lock( &conflist_lock ) ; + + for ( conf = conflist ; conf != NULL ; conf = conf->next ) + { + if ( strcmp(conference, conf->name) == 0 ) + { + // acquire conference mutex + ast_mutex_lock( &conf->lock ); + + for ( member = conf->memberlist ; member != NULL ; member = member->next ) + { + if ( member->id == member_id ) + { + // acquire member mutex + ast_mutex_lock( &member->lock ); + + member->mute_video = 0; + + // release member mutex + ast_mutex_unlock( &member->lock ); + + manager_event(EVENT_FLAG_CALL, "ConferenceVideoUnmute", "ConferenceName: %s\r\nChannel: %s\r\n", conf->name, member->channel_name); + + res = 1; + break; + } + } + + // release conference mutex + ast_mutex_unlock( &conf->lock ); + + break; + } + } + + // release conference list mutex + ast_mutex_unlock( &conflist_lock ) ; + + return res; +} + +int video_mute_channel(const char *conference, const char *channel) +{ + struct ast_conference *conf; + struct ast_conf_member *member; + int res; + + if ( conference == NULL || channel == NULL ) + return -1; + + res = 0; + + // acquire conference list mutex + ast_mutex_lock( &conflist_lock ) ; + + for ( conf = conflist ; conf != NULL ; conf = conf->next ) + { + if ( strcmp(conference, conf->name) == 0 ) + { + // acquire conference mutex + ast_mutex_lock( &conf->lock ); + + for ( member = conf->memberlist ; member != NULL ; member = member->next ) + { + if ( strcmp(channel, member->channel_name) == 0 ) + { + // acquire member mutex + ast_mutex_lock( &member->lock ); + + member->mute_video = 1; + + // release member mutex + ast_mutex_unlock( &member->lock ); + + manager_event(EVENT_FLAG_CALL, "ConferenceVideoMute", "ConferenceName: %s\r\nChannel: %s\r\n", conf->name, member->channel_name); + + if ( member->id == conf->current_video_source_id ) + { + do_video_switching(conf, conf->default_video_source_id, 0); + } + + res = 1; + break; + } + } + + // release conference mutex + ast_mutex_unlock( &conf->lock ); + + break; + } + } + + // release conference list mutex + ast_mutex_unlock( &conflist_lock ) ; + + return res; +} + +int video_unmute_channel(const char *conference, const char *channel) +{ + struct ast_conference *conf; + struct ast_conf_member *member; + int res; + + if ( conference == NULL || channel == NULL ) + return -1; + + res = 0; + + // acquire conference list mutex + ast_mutex_lock( &conflist_lock ) ; + + for ( conf = conflist ; conf != NULL ; conf = conf->next ) + { + if ( strcmp(conference, conf->name) == 0 ) + { + // acquire conference mutex + ast_mutex_lock( &conf->lock ); + + for ( member = conf->memberlist ; member != NULL ; member = member->next ) + { + if ( strcmp(channel, member->channel_name) == 0 ) + { + // acquire member mutex + ast_mutex_lock( &member->lock ); + + member->mute_video = 0; + + // release member mutex + ast_mutex_unlock( &member->lock ); + + manager_event(EVENT_FLAG_CALL, "ConferenceVideoUnmute", "ConferenceName: %s\r\nChannel: %s\r\n", conf->name, member->channel_name); + + res = 1; + break; + } + } + + // release conference mutex + ast_mutex_unlock( &conf->lock ); + + break; + } + } + + // release conference list mutex + ast_mutex_unlock( &conflist_lock ) ; + + return res; +} + +// +// Text message functions +// +int send_text(const char *conference, int member_id, const char *text) +{ + struct ast_conference *conf; + struct ast_conf_member *member; + int res; + + if ( conference == NULL || member_id < 0 || text == NULL ) + return -1; + + res = 0; + + // acquire conference list mutex + ast_mutex_lock( &conflist_lock ) ; + + for ( conf = conflist ; conf != NULL ; conf = conf->next ) + { + if ( strcmp(conference, conf->name) == 0 ) + { + // acquire conference mutex + ast_mutex_lock( &conf->lock ); + + for ( member = conf->memberlist ; member != NULL ; member = member->next ) + { + if ( member->id == member_id ) + { + res = send_text_message_to_member(member, text) == 0; + break; + } + } + + // release conference mutex + ast_mutex_unlock( &conf->lock ); + + break; + } + } + + // release conference list mutex + ast_mutex_unlock( &conflist_lock ) ; + return res; +} + +int send_text_channel(const char *conference, const char *channel, const char *text) +{ + struct ast_conference *conf; + struct ast_conf_member *member; + int res; + + if ( conference == NULL || channel == NULL || text == NULL ) + return -1; + + res = 0; + + // acquire conference list mutex + ast_mutex_lock( &conflist_lock ) ; + + for ( conf = conflist ; conf != NULL ; conf = conf->next ) + { + if ( strcmp(conference, conf->name) == 0 ) + { + // acquire conference mutex + ast_mutex_lock( &conf->lock ); + + for ( member = conf->memberlist ; member != NULL ; member = member->next ) + { + if ( strcmp(member->channel_name, channel) == 0 ) + { + res = send_text_message_to_member(member, text) == 0; + break; + } + } + + // release conference mutex + ast_mutex_unlock( &conf->lock ); + + break; + } + } + + // release conference list mutex + ast_mutex_unlock( &conflist_lock ) ; + + return res; +} + +int send_text_broadcast(const char *conference, const char *text) +{ + struct ast_conference *conf; + struct ast_conf_member *member; + int res; + + if ( conference == NULL || text == NULL ) + return -1; + + res = 0; + + // acquire conference list mutex + ast_mutex_lock( &conflist_lock ) ; + + for ( conf = conflist ; conf != NULL ; conf = conf->next ) + { + if ( strcmp(conference, conf->name) == 0 ) + { + // acquire conference mutex + ast_mutex_lock( &conf->lock ); + + for ( member = conf->memberlist ; member != NULL ; member = member->next ) + { + if ( send_text_message_to_member(member, text) == 0 ) + res = res || 1; + } + + // release conference mutex + ast_mutex_unlock( &conf->lock ); + + break; + } + } + + // release conference list mutex + ast_mutex_unlock( &conflist_lock ) ; + + return res; +} + +// Creates a text frame and sends it to a given member +// Returns 0 on success, -1 on failure +int send_text_message_to_member(struct ast_conf_member *member, const char *text) +{ + struct ast_frame *f; + + if ( member == NULL || text == NULL ) return -1; + + if ( member->does_text ) + { + f = create_text_frame(text, 1); + if ( f == NULL || queue_outgoing_text_frame(member, f) != 0) return -1; + ast_frfree(f); + } + + return 0; +} + +// Associates two members +// Drives VAD-based video switching of dst_member from audio from src_member +// This can be used when a member participates in a video conference but +// talks using a telephone (simulcast) connection +int drive(const char *conference, int src_member_id, int dst_member_id) +{ + int res; + struct ast_conference *conf; + struct ast_conf_member *member; + struct ast_conf_member *src; + struct ast_conf_member *dst; + + if ( conference == NULL || src_member_id < 0 ) + return -1; + + res = 0; + + // acquire conference list mutex + ast_mutex_lock( &conflist_lock ) ; + + for ( conf = conflist ; conf != NULL ; conf = conf->next ) + { + if ( strcmp(conference, conf->name) == 0 ) + { + // acquire conference mutex + ast_mutex_lock( &conf->lock ); + + src = NULL; + dst = NULL; + for ( member = conf->memberlist ; member != NULL ; member = member->next ) + { + if ( member->id == src_member_id ) + src = member; + if ( member->id == dst_member_id ) + dst = member; + } + if ( src != NULL ) + { + // acquire member mutex + ast_mutex_lock(&src->lock); + + if ( dst != NULL ) + { + src->driven_member = dst; + // Make sure the driven member's speaker count is correct + if ( src->speaking_state == 1 ) + increment_speaker_count(src->driven_member, 1); + res = 1; + } else + { + if ( dst_member_id < 0 ) + { + // Make sure the driven member's speaker count is correct + if ( src->speaking_state == 1 ) + decrement_speaker_count(src->driven_member, 1); + src->driven_member = NULL; + res = 1; + } + } + + // release member mutex + ast_mutex_unlock(&src->lock); + } + + // release conference mutex + ast_mutex_unlock( &conf->lock ); + + break; + } + } + + // release conference list mutex + ast_mutex_unlock( &conflist_lock ) ; + + return res; +} + +// Associates two channels +// Drives VAD-based video switching of dst_channel from audio from src_channel +// This can be used when a member participates in a video conference but +// talks using a telephone (simulcast) connection +int drive_channel(const char *conference, const char *src_channel, const char *dst_channel) +{ + int res; + struct ast_conference *conf; + struct ast_conf_member *member; + struct ast_conf_member *src; + struct ast_conf_member *dst; + + if ( conference == NULL || src_channel == NULL ) + return -1; + + res = 0; + + // acquire conference list mutex + ast_mutex_lock( &conflist_lock ) ; + + for ( conf = conflist ; conf != NULL ; conf = conf->next ) + { + if ( strcmp(conference, conf->name) == 0 ) + { + // acquire conference mutex + ast_mutex_lock( &conf->lock ); + + src = NULL; + dst = NULL; + for ( member = conf->memberlist ; member != NULL ; member = member->next ) + { + if ( strcmp(src_channel, member->channel_name) == 0 ) + src = member; + if ( dst_channel != NULL && strcmp(dst_channel, member->channel_name) == 0 ) + dst = member; + } + if ( src != NULL ) + { + // acquire member mutex + ast_mutex_lock(&src->lock); + + if ( dst != NULL ) + { + src->driven_member = dst; + // Make sure the driven member's speaker count is correct + if ( src->speaking_state == 1 ) + increment_speaker_count(src->driven_member, 1); + res = 1; + } else + { + if ( dst_channel == NULL ) + { + // Make sure the driven member's speaker count is correct + if ( src->speaking_state == 1 ) + decrement_speaker_count(src->driven_member, 1); + src->driven_member = NULL; + res = 1; + } + } + + // release member mutex + ast_mutex_unlock(&src->lock); + } + + // release conference mutex + ast_mutex_unlock( &conf->lock ); + + break; + } + } + + // release conference list mutex + ast_mutex_unlock( &conflist_lock ) ; + + return res; +} + +// Switches video source +// Sends a manager event as well as +// a text message notifying members of a video switch +// The notification is sent to the current member and to the new member +// The function locks the conference mutex as required +void do_video_switching(struct ast_conference *conf, int new_id, int lock) +{ + struct ast_conf_member *member; + struct ast_conf_member *new_member = NULL; + + if ( conf == NULL ) return; + + if ( lock ) + { + // acquire conference mutex + ast_mutex_lock( &conf->lock ); + } + + //fprintf(stderr, "Mihai: video switch from %d to %d\n", conf->current_video_source_id, new_id); + + // No need to do anything if the current member is the same as the new member + if ( new_id != conf->current_video_source_id ) + { + for ( member = conf->memberlist ; member != NULL ; member = member->next ) + { + if ( member->id == conf->current_video_source_id ) + { + send_text_message_to_member(member, AST_CONF_CONTROL_STOP_VIDEO); + } + if ( member->id == new_id ) + { + send_text_message_to_member(member, AST_CONF_CONTROL_START_VIDEO); + new_member = member; + } + } + + conf->current_video_source_id = new_id; + + if ( new_member != NULL ) + { + manager_event(EVENT_FLAG_CALL, + "ConferenceVideoSwitch", + "ConferenceName: %s\r\nChannel: %s\r\n", + conf->name, + new_member->channel_name); + } else + { + manager_event(EVENT_FLAG_CALL, + "ConferenceVideoSwitch", + "ConferenceName: %s\r\nChannel: empty\r\n", + conf->name); + } + } + + if ( lock ) + { + // release conference mutex + ast_mutex_unlock( &conf->lock ); + } +} + +int basic_play_sound(struct ast_conf_member *member, const char *file, int mute) +{ + struct ast_conf_soundq *newsound; + struct ast_conf_soundq **q; + + newsound = calloc(1, sizeof(struct ast_conf_soundq)); + newsound->stream = ast_openstream(member->chan, file, NULL); + if( !newsound->stream ) + { + free(newsound); + ast_mutex_unlock(&member->lock); + return 0; + } + member->chan->stream = NULL; + + newsound->muted = mute; + ast_copy_string(newsound->name, file, sizeof(newsound->name)); + + // append sound to the end of the list. + for ( q=&member->soundq; *q; q = &((*q)->next) ) ; + *q = newsound; + + ast_mutex_unlock(&member->lock); + + return 1 ; +} + +int play_sound_channel(int fd, const char *channel, const char *file, int mute) +{ + struct ast_conf_member *member; + + member = find_member(channel, 1); + if( !member ) + { + ast_cli(fd, "Member %s not found\n", channel); + return 0; + } + + if (! basic_play_sound(member, file, mute)) + { + ast_cli(fd, "Sound %s not found\n", file); + return 0; + } + + ast_cli(fd, "Playing sound %s to member %s %s\n", + file, channel, mute ? "with mute" : ""); + + return 1 ; +} + +int stop_sound_channel(int fd, const char *channel) +{ + struct ast_conf_member *member; + struct ast_conf_soundq *sound; + struct ast_conf_soundq *next; + + member = find_member(channel, 1); + if ( !member ) + { + ast_cli(fd, "Member %s not found\n", channel); + return 0; + } + + // clear all sounds + sound = member->soundq; + member->soundq = NULL; + + while ( sound ) + { + next = sound->next; + ast_closestream(sound->stream); + free(sound); + sound = next; + } + + // reset write format, since we're done playing the sound + if ( ast_set_write_format( member->chan, member->write_format ) < 0 ) + { + ast_log( LOG_ERROR, "unable to set write format to %d\n", + member->write_format ) ; + } + + ast_mutex_unlock(&member->lock); + + ast_cli( fd, "Stopped sounds to member %s\n", channel); + return 1; +} diff --git a/apps/conference/conference.h b/apps/conference/conference.h new file mode 100644 index 0000000..0f7e248 --- /dev/null +++ b/apps/conference/conference.h @@ -0,0 +1,194 @@ + +// $Id: conference.h 884 2007-06-27 14:56:21Z sbalea $ + +/* + * app_conference + * + * A channel independent conference application for Asterisk + * + * Copyright (C) 2002, 2003 Junghanns.NET GmbH + * Copyright (C) 2003, 2004 HorizonLive.com, Inc. + * Copyright (C) 2005, 2006 HorizonWimba, Inc. + * Copyright (C) 2007 Wimba, Inc. + * + * Klaus-Peter Junghanns + * + * Video Conferencing support added by + * Neil Stratford + * Copyright (C) 2005, 2005 Vipadia Limited + * + * VAD driven video conferencing, text message support + * and miscellaneous enhancements added by + * Mihai Balea + * + * This program may be modified and distributed under the + * terms of the GNU General Public License. You should have received + * a copy of the GNU General Public License along with this + * program; if not, write to the Free Software Foundation, Inc. + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _APP_CONF_CONFERENCE_H +#define _APP_CONF_CONFERENCE_H + +// +// includes +// + +#include "app_conference.h" +#include "common.h" + +// +// struct declarations +// + +typedef struct ast_conference_stats +{ + // conference name ( copied for ease of use ) + char name[128] ; + + // type of connection + unsigned short phone ; + unsigned short iaxclient ; + unsigned short sip ; + + // type of users + unsigned short moderators ; + unsigned short listeners ; + + // accounting data + unsigned long frames_in ; + unsigned long frames_out ; + unsigned long frames_mixed ; + + struct timeval time_entered ; + +} ast_conference_stats ; + + +struct ast_conference +{ + // conference name + char name[128] ; + + // single-linked list of members in conference + struct ast_conf_member* memberlist ; + int membercount ; + int id_count; + + // id of the default video source + // If nobody is talking and video is unlocked, we use this source + int default_video_source_id; + + // id of the current video source + // this changes according to VAD rules and lock requests + int current_video_source_id; + + // timestamp of when the current source has started talking + // TODO: do we really need this? + //struct timeval current_video_source_timestamp; + + // Video source locked flag, 1 -> locked, 0 -> unlocked + short video_locked; + + // conference thread id + pthread_t conference_thread ; + + // conference data mutex + ast_mutex_t lock ; + + // pointer to next conference in single-linked list + struct ast_conference* next ; + + // pointer to translation paths + struct ast_trans_pvt* from_slinear_paths[ AC_SUPPORTED_FORMATS ] ; + + // conference stats + ast_conference_stats stats ; + + // keep track of current delivery time + struct timeval delivery_time ; + + // 1 => on, 0 => off + short debug_flag ; +} ; + + +#include "member.h" + +// +// function declarations +// + +struct ast_conference* start_conference( struct ast_conf_member* member ) ; + +void conference_exec( struct ast_conference* conf ) ; + +struct ast_conference* find_conf( const char* name ) ; +struct ast_conference* create_conf( char* name, struct ast_conf_member* member ) ; +void remove_conf( struct ast_conference* conf ) ; +int end_conference( const char *name, int hangup ) ; + +// find a particular member, locking if requested. +struct ast_conf_member *find_member ( const char *chan, int lock) ; + +int queue_frame_for_listener( struct ast_conference* conf, struct ast_conf_member* member, conf_frame* frame ) ; +int queue_frame_for_speaker( struct ast_conference* conf, struct ast_conf_member* member, conf_frame* frame ) ; +int queue_silent_frame( struct ast_conference* conf, struct ast_conf_member* member ) ; + +int get_new_id( struct ast_conference *conf ); +void add_member( struct ast_conf_member* member, struct ast_conference* conf ) ; +int remove_member( struct ast_conf_member* member, struct ast_conference* conf ) ; +int count_member( struct ast_conf_member* member, struct ast_conference* conf, short add_member ) ; + +void do_VAD_switching(struct ast_conference *conf); +int send_text_message_to_member(struct ast_conf_member *member, const char *text); +void do_video_switching(struct ast_conference *conf, int new_id, int lock); + +// called by app_confernce.c:load_module() +void init_conference( void ) ; + +int get_conference_count( void ) ; + +int show_conference_list ( int fd, const char* name ); +int manager_conference_list( struct mansession *s, const struct message *m); +int show_conference_stats ( int fd ); +int kick_member ( const char* confname, int user_id); +int kick_channel ( const char *confname, const char *channel); +int kick_all ( void ); +int mute_member ( const char* confname, int user_id); +int unmute_member ( const char* confname, int user_id); +int mute_channel ( const char* confname, const char* user_chan); +int unmute_channel ( const char* confname, const char* user_chan); +int viewstream_switch ( const char* confname, int user_id, int stream_id); +int viewchannel_switch ( const char* confname, const char* user_chan, const char* stream_chan); + +int get_conference_stats( ast_conference_stats* stats, int requested ) ; +int get_conference_stats_by_name( ast_conference_stats* stats, const char* name ) ; + +int lock_conference(const char *conference, int member_id); +int lock_conference_channel(const char *conference, const char *channel); +int unlock_conference(const char *conference); + +int set_default_id(const char *conference, int member_id); +int set_default_channel(const char *conference, const char *channel); + +int video_mute_member(const char *conference, int member_id); +int video_unmute_member(const char *conference, int member_id); +int video_mute_channel(const char *conference, const char *channel); +int video_unmute_channel(const char *conference, const char *channel); + +int send_text(const char *conference, int member, const char *text); +int send_text_channel(const char *conference, const char *channel, const char *text); +int send_text_broadcast(const char *conference, const char *text); + +int drive(const char *conference, int src_member_id, int dst_member_id); +int drive_channel(const char *conference, const char *src_channel, const char *dst_channel); + +int basic_play_sound(struct ast_conf_member *member, const char *file, int mute); +int play_sound_channel(int fd, const char *channel, const char *file, int mute); +int stop_sound_channel(int fd, const char *channel); + +int set_conference_debugging( const char* name, int state ) ; + +#endif diff --git a/apps/conference/frame.c b/apps/conference/frame.c new file mode 100644 index 0000000..24e04bd --- /dev/null +++ b/apps/conference/frame.c @@ -0,0 +1,679 @@ + +// $Id: frame.c 751 2006-12-11 22:08:45Z sbalea $ + +/* + * app_conference + * + * A channel independent conference application for Asterisk + * + * Copyright (C) 2002, 2003 Junghanns.NET GmbH + * Copyright (C) 2003, 2004 HorizonLive.com, Inc. + * Copyright (C) 2005, 2006 HorizonWimba, Inc. + * Copyright (C) 2007 Wimba, Inc. + * + * Klaus-Peter Junghanns + * + * Video Conferencing support added by + * Neil Stratford + * Copyright (C) 2005, 2005 Vipadia Limited + * + * VAD driven video conferencing, text message support + * and miscellaneous enhancements added by + * Mihai Balea + * + * This program may be modified and distributed under the + * terms of the GNU General Public License. You should have received + * a copy of the GNU General Public License along with this + * program; if not, write to the Free Software Foundation, Inc. + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "asterisk/autoconfig.h" +#include "frame.h" + +conf_frame* mix_frames( conf_frame* frames_in, int speaker_count, int listener_count ) +{ + if ( frames_in == NULL ) + return NULL ; + + conf_frame* frames_out = NULL ; + + if ( speaker_count > 1 ) + { + if ( speaker_count == 2 && listener_count == 0 ) + { + // optimize here also? + frames_out = mix_multiple_speakers( frames_in, speaker_count, listener_count ) ; + } + else + { + // mix spoken frames for sending + // ( note: this call also releases us from free'ing spoken_frames ) + frames_out = mix_multiple_speakers( frames_in, speaker_count, listener_count ) ; + } + } + else if ( speaker_count == 1 ) + { + // pass-through frames + frames_out = mix_single_speaker( frames_in ) ; + //printf("mix single speaker\n"); + } + else + { + // no frames to send, leave frames_out null + } + + return frames_out ; +} + +conf_frame* mix_single_speaker( conf_frame* frames_in ) +{ +#ifdef APP_CONFERENCE_DEBUG + // ast_log( AST_CONF_DEBUG, "returning single spoken frame\n" ) ; + + // + // check input + // + + if ( frames_in == NULL ) + { + ast_log( AST_CONF_DEBUG, "unable to mix single spoken frame with null frame\n" ) ; + return NULL ; + } + + if ( frames_in->fr == NULL ) + { + ast_log( AST_CONF_DEBUG, "unable to mix single spoken frame with null data\n" ) ; + return NULL ; + } + + if ( frames_in->member == NULL ) + { + ast_log( AST_CONF_DEBUG, "unable to mix single spoken frame with null member\n" ) ; + return NULL ; + } +#endif // APP_CONFERENCE_DEBUG + + // + // 'mix' the frame + // + + // copy orignal frame to converted array so listeners don't need to re-encode it + frames_in->converted[ frames_in->member->read_format_index ] = ast_frdup( frames_in->fr ) ; + + // convert frame to slinear, if we have a path + frames_in->fr = convert_frame_to_slinear( + frames_in->member->to_slinear, + frames_in->fr + ) ; + + // set the frame's member to null ( i.e. all listeners ) + frames_in->member = NULL ; + + return frames_in ; +} + +// { + // + // a little optimization for testing only: + // when two speakers ( of the same type ) and no listeners + // are in a conference, we just swamp the frame's member pointers + // +/* + if ( + listeners == 0 + && speakers == 2 + && cf_spokenFrames->member->read_format == cf_spokenFrames->next->member->write_format + && cf_spokenFrames->member->write_format == cf_spokenFrames->next->member->read_format + ) + { + struct ast_conf_member* m = NULL ; + m = cf_spokenFrames->member ; + cf_spokenFrames->member = cf_spokenFrames->next->member ; + cf_spokenFrames->next->member = m ; + return cf_spokenFrames ; + } +*/ +// } + +void set_conf_frame_delivery( conf_frame* frame, struct timeval time ) +{ + for ( ; frame != NULL ; frame = frame->next ) + { + if ( frame->fr != NULL ) + { + // copy passed timeval to frame's delivery timeval + frame->fr->delivery = time ; + } + } + + return ; +} + +conf_frame* mix_multiple_speakers( + conf_frame* frames_in, + int speakers, + int listeners +) +{ +#ifdef APP_CONFERENCE_DEBUG + // + // check input + // + + // no frames to mix + if ( ( frames_in == NULL ) || ( frames_in->fr == NULL ) ) + { + ast_log( AST_CONF_DEBUG, "passed spoken frame list was NULL\n" ) ; + return NULL ; + } + + // if less than two speakers, then no frames to mix + if ( speakers < 2 ) + { + ast_log( AST_CONF_DEBUG, "mix_multiple_speakers() called with less than two speakers\n" ) ; + return NULL ; + } +#endif // APP_CONFERENCE_DEBUG + + // + // at this point we know that there is more than one frame, + // and that the frames need to be converted to pcm to be mixed + // + // now, if there are only two frames and two members, + // we can swap them. ( but we'll get to that later. ) + // + + // + // loop through the spoken frames, making a list of spoken members, + // and converting gsm frames to slinear frames so we can mix them. + // + + // pointer to the spoken frames list + conf_frame* cf_spoken = frames_in ; + + // pointer to the new list of mixed frames + conf_frame* cf_sendFrames = NULL ; + + while ( cf_spoken != NULL ) + { + // + // while we're looping through the spoken frames, we'll + // convert the frame to a format suitable for mixing + // + // if the frame fails to convert, drop it and treat + // the speaking member like a listener by not adding + // them to the cf_sendFrames list + // + + if ( cf_spoken->member == NULL ) + { + ast_log( LOG_WARNING, "unable to determine frame member\n" ) ; + } + else + { + // ast_log( AST_CONF_DEBUG, "converting frame to slinear, channel => %s\n", cf_spoken->member->channel_name ) ; + cf_spoken->fr = convert_frame_to_slinear( + cf_spoken->member->to_slinear, + cf_spoken->fr + ) ; + + if ( cf_spoken->fr == NULL ) + { + ast_log( LOG_WARNING, "unable to convert frame to slinear\n" ) ; + } + else + { + // create new conf frame with last frame as 'next' + cf_sendFrames = create_conf_frame( cf_spoken->member, cf_sendFrames, NULL ) ; + } + } + + // point to the next spoken frame + cf_spoken = cf_spoken->next ; + } + + // if necessary, add a frame with a null member pointer. + // this frame will hold the audio mixed for all listeners + if ( listeners > 0 ) + { + cf_sendFrames = create_conf_frame( NULL, cf_sendFrames, NULL ) ; + } + + // + // mix the audio + // + + // convenience pointer that skips over the friendly offset + char* cp_listenerData ; + + // pointer to the send frames list + conf_frame* cf_send = NULL ; + + for ( cf_send = cf_sendFrames ; cf_send != NULL ; cf_send = cf_send->next ) + { + // allocate a mix buffer which fill large enough memory to + // hold a frame, and reset it's memory so we don't get noise + char* cp_listenerBuffer = malloc( AST_CONF_BUFFER_SIZE ) ; + memset( cp_listenerBuffer, 0x0, AST_CONF_BUFFER_SIZE ) ; + + // point past the friendly offset right to the data + cp_listenerData = cp_listenerBuffer + AST_FRIENDLY_OFFSET ; + + // reset the spoken list pointer + cf_spoken = frames_in ; + + // really mix the audio + for ( ; cf_spoken != NULL ; cf_spoken = cf_spoken->next ) + { + // + // if the members are equal, and they + // are not null, do not mix them. + // + if ( + ( cf_send->member == cf_spoken->member ) + && ( cf_send->member != NULL ) + ) + { + // don't mix this frame + } + else if ( cf_spoken->fr == NULL ) + { + ast_log( LOG_WARNING, "unable to mix conf_frame with null ast_frame\n" ) ; + } + else + { + // mix the new frame in with the existing buffer + mix_slinear_frames( cp_listenerData, (char*)( cf_spoken->fr->data ), AST_CONF_BLOCK_SAMPLES);//XXX NAS cf_spoken->fr->samples ) ; + } + } + + // copy a pointer to the frame data to the conf_frame + cf_send->mixed_buffer = cp_listenerData ; + } + + // + // copy the mixed buffer to a new frame + // + + // reset the send list pointer + cf_send = cf_sendFrames ; + + while ( cf_send != NULL ) + { + cf_send->fr = create_slinear_frame( cf_send->mixed_buffer ) ; + cf_send = cf_send->next ; + } + + // + // clean up the spoken frames we were passed + // ( caller will only be responsible for free'ing returns frames ) + // + + // reset the spoken list pointer + cf_spoken = frames_in ; + + while ( cf_spoken != NULL ) + { + // delete the frame + cf_spoken = delete_conf_frame( cf_spoken ) ; + } + + // return the list of frames for sending + return cf_sendFrames ; +} + + +struct ast_frame* convert_frame_to_slinear( struct ast_trans_pvt* trans, struct ast_frame* fr ) +{ + // check for null frame + if ( fr == NULL ) + { + ast_log( LOG_ERROR, "unable to translate null frame to slinear\n" ) ; + return NULL ; + } + + // we don't need to duplicate this frame since + // the normal translation would free it anyway, so + // we'll just pretend we free'd and malloc'd a new one. + if ( fr->subclass == AST_FORMAT_SLINEAR ) + return fr ; + + // check for null translator ( after we've checked that we need to translate ) + if ( trans == NULL ) + { + ast_log( LOG_ERROR, "unable to translate frame with null translation path\n" ) ; + return fr ; + } + + // return the converted frame + return convert_frame( trans, fr ) ; +} + +struct ast_frame* convert_frame_from_slinear( struct ast_trans_pvt* trans, struct ast_frame* fr ) +{ + // check for null translator ( after we've checked that we need to translate ) + if ( trans == NULL ) + { + //ast_log( LOG_ERROR, "unable to translate frame with null translation path\n" ) ; + return fr ; + } + + // check for null frame + if ( fr == NULL ) + { + ast_log( LOG_ERROR, "unable to translate null slinear frame\n" ) ; + return NULL ; + } + + // if the frame is not slinear, return an error + if ( fr->subclass != AST_FORMAT_SLINEAR ) + { + ast_log( LOG_ERROR, "unable to translate non-slinear frame\n" ) ; + return NULL ; + } + + // return the converted frame + return convert_frame( trans, fr ) ; +} + +struct ast_frame* convert_frame( struct ast_trans_pvt* trans, struct ast_frame* fr ) +{ + if ( trans == NULL ) + { + ast_log( LOG_WARNING, "unable to convert frame with null translator\n" ) ; + return NULL ; + } + + if ( fr == NULL ) + { + ast_log( LOG_WARNING, "unable to convert null frame\n" ) ; + return NULL ; + } + + // convert the frame + struct ast_frame* translated_frame = ast_translate( trans, fr, 1 ) ; + + // check for errors + if ( translated_frame == NULL ) + { + ast_log( LOG_ERROR, "unable to translate frame\n" ) ; + return NULL ; + } + + // return the translated frame + return translated_frame ; +} + +conf_frame* delete_conf_frame( conf_frame* cf ) +{ + int c; + // check for null frames + if ( cf == NULL ) + { + ast_log( AST_CONF_DEBUG, "unable to delete null conf frame\n" ) ; + return NULL ; + } + + // check for frame marked as static + if ( cf->static_frame == 1 ) + return NULL ; + + if ( cf->fr != NULL ) + { + ast_frfree( cf->fr ) ; + cf->fr = NULL ; + } + + // make sure converted frames are set to null + for ( c = 0 ; c < AC_SUPPORTED_FORMATS ; ++c ) + { + if ( cf->converted[ c ] != NULL ) + { + ast_frfree( cf->converted[ c ] ) ; + cf->converted[ c ] = NULL ; + } + } + + // get a pointer to the next frame + // in the list so we can return it + conf_frame* nf = cf->next ; + + free( cf ) ; + cf = NULL ; + + return nf ; +} + +conf_frame* create_conf_frame( struct ast_conf_member* member, conf_frame* next, const struct ast_frame* fr ) +{ + // pointer to list of mixed frames + conf_frame* cf = malloc( sizeof( struct conf_frame ) ) ; + + if ( cf == NULL ) + { + ast_log( LOG_ERROR, "unable to allocate memory for conf frame\n" ) ; + return NULL ; + } + + // + // init with some defaults + // + + // make sure converted frames are set to null +// for ( int c = 0 ; c < AC_SUPPORTED_FORMATS ; ++c ) +// { +// cf->converted[ c ] = NULL ; +// } + + memset( (struct ast_frame*)( cf->converted ), 0x0, ( sizeof( struct ast_frame* ) * AC_SUPPORTED_FORMATS ) ) ; + + cf->member = member ; + // cf->priority = 0 ; + + cf->prev = NULL ; + cf->next = next ; + + cf->static_frame = 0 ; + + // establish relationship to 'next' + if ( next != NULL ) next->prev = cf ; + + // this holds the ast_frame pointer + cf->fr = ( fr == NULL ) ? NULL : ast_frdup( ( struct ast_frame* )( fr ) ) ; + + // this holds the temporu mix buffer + cf->mixed_buffer = NULL ; + + return cf ; +} + +conf_frame* copy_conf_frame( conf_frame* src ) +{ + // + // check inputs + // + + if ( src == NULL ) + { + ast_log( AST_CONF_DEBUG, "unable to copy null conf frame\n" ) ; + return NULL ; + } + + // + // copy the frame + // + + struct conf_frame *cfr = NULL ; + + // create a new conf frame + cfr = create_conf_frame( src->member, NULL, src->fr ) ; + + if ( cfr == NULL ) + { + ast_log( AST_CONF_DEBUG, "unable to create new conf frame for copy\n" ) ; + return NULL ; + } + + return cfr ; +} + +// +// Create a TEXT frame based on a given string +// +struct ast_frame* create_text_frame(const char *text, int copy) +{ + struct ast_frame *f; + char *t; + + f = calloc(1, sizeof(struct ast_frame)); + if ( f == NULL ) + { + ast_log( LOG_ERROR, "unable to allocate memory for text frame\n" ) ; + return NULL ; + } + if ( copy ) + { + t = calloc(strlen(text) + 1, 1); + if ( t == NULL ) + { + ast_log( LOG_ERROR, "unable to allocate memory for text data\n" ) ; + free(f); + return NULL ; + } + strncpy(t, text, strlen(text)); + } else + { + t = (char *)text; + } + + f->frametype = AST_FRAME_TEXT; + f->offset = 0; + f->mallocd = AST_MALLOCD_HDR; + if ( copy ) f->mallocd |= AST_MALLOCD_DATA; + f->datalen = strlen(t) + 1; + f->data = t; + f->src = NULL; + + return f; +} + +// +// slinear frame functions +// + +struct ast_frame* create_slinear_frame( char* data ) +{ + struct ast_frame* f ; + + f = calloc( 1, sizeof( struct ast_frame ) ) ; + if ( f == NULL ) + { + ast_log( LOG_ERROR, "unable to allocate memory for slinear frame\n" ) ; + return NULL ; + } + + f->frametype = AST_FRAME_VOICE ; + f->subclass = AST_FORMAT_SLINEAR ; + f->samples = AST_CONF_BLOCK_SAMPLES ; + f->offset = AST_FRIENDLY_OFFSET ; + f->mallocd = AST_MALLOCD_HDR | AST_MALLOCD_DATA ; + + f->datalen = AST_CONF_FRAME_DATA_SIZE ; + f->data = data ; + + f->src = NULL ; + + return f ; +} + +void mix_slinear_frames( char *dst, const char *src, int samples ) +{ + if ( dst == NULL ) return ; + if ( src == NULL ) return ; + + int i, val ; + + for ( i = 0 ; i < samples ; ++i ) + { + val = ( (short*)dst )[i] + ( (short*)src )[i] ; + + if ( val > 0x7fff ) + { + ( (short*)dst )[i] = 0x7fff - 1 ; + continue ; + } + else if ( val < -0x7fff ) + { + ( (short*)dst )[i] = -0x7fff + 1 ; + continue ; + } + else + { + ( (short*)dst )[i] = val ; + continue ; + } + } + + return ; +} + +// +// silent frame functions +// + +conf_frame* get_silent_frame( void ) +{ + static conf_frame* static_silent_frame = NULL ; + + // we'll let this leak until the application terminates + if ( static_silent_frame == NULL ) + { + // ast_log( AST_CONF_DEBUG, "creating cached silent frame\n" ) ; + struct ast_frame* fr = get_silent_slinear_frame() ; + + static_silent_frame = create_conf_frame( NULL, NULL, fr ) ; + + if ( static_silent_frame == NULL ) + { + ast_log( LOG_WARNING, "unable to create cached silent frame\n" ) ; + return NULL ; + } + + // init the 'converted' slinear silent frame + static_silent_frame->converted[ AC_SLINEAR_INDEX ] = get_silent_slinear_frame() ; + + // mark frame as static so it's not deleted + static_silent_frame->static_frame = 1 ; + } + + return static_silent_frame ; +} + +struct ast_frame* get_silent_slinear_frame( void ) +{ + static struct ast_frame* f = NULL ; + + // we'll let this leak until the application terminates + if ( f == NULL ) + { + char* data = malloc( AST_CONF_BUFFER_SIZE ) ; + memset( data, 0x0, AST_CONF_BUFFER_SIZE ) ; + f = create_slinear_frame( data ) ; + } + + return f; +} + + + + + + + + + + + + + diff --git a/apps/conference/frame.h b/apps/conference/frame.h new file mode 100644 index 0000000..2b77805 --- /dev/null +++ b/apps/conference/frame.h @@ -0,0 +1,75 @@ + +// $Id: frame.h 746 2006-12-11 20:12:12Z sbalea $ + +/* + * app_conference + * + * A channel independent conference application for Asterisk + * + * Copyright (C) 2002, 2003 Junghanns.NET GmbH + * Copyright (C) 2003, 2004 HorizonLive.com, Inc. + * Copyright (C) 2005, 2006 HorizonWimba, Inc. + * Copyright (C) 2007 Wimba, Inc. + * + * Klaus-Peter Junghanns + * + * Video Conferencing support added by + * Neil Stratford + * Copyright (C) 2005, 2005 Vipadia Limited + * + * VAD driven video conferencing, text message support + * and miscellaneous enhancements added by + * Mihai Balea + * + * This program may be modified and distributed under the + * terms of the GNU General Public License. You should have received + * a copy of the GNU General Public License along with this + * program; if not, write to the Free Software Foundation, Inc. + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _APP_CONF_FRAME_H +#define _APP_CONF_FRAME_H + +// +// includes +// + +#include "app_conference.h" +#include "common.h" + +// +// function declarations +// + +// mixing +conf_frame* mix_frames( conf_frame* frames_in, int speaker_count, int listener_count ) ; + +conf_frame* mix_multiple_speakers( conf_frame* frames_in, int speakers, int listeners ) ; +conf_frame* mix_single_speaker( conf_frame* frames_in ) ; + +// frame creation and deletion +conf_frame* create_conf_frame( struct ast_conf_member* member, conf_frame* next, const struct ast_frame* fr ) ; +conf_frame* delete_conf_frame( conf_frame* cf ) ; +conf_frame* copy_conf_frame( conf_frame* src ) ; + +// convert frame functions +struct ast_frame* convert_frame_to_slinear( struct ast_trans_pvt* trans, struct ast_frame* fr ) ; +struct ast_frame* convert_frame_from_slinear( struct ast_trans_pvt* trans, struct ast_frame* fr ) ; +struct ast_frame* convert_frame( struct ast_trans_pvt* trans, struct ast_frame* fr ) ; + +// text frame function(s) +struct ast_frame* create_text_frame(const char *text, int copy); + +// slinear frame functions +struct ast_frame* create_slinear_frame( char* data ) ; +void mix_slinear_frames( char* dst, const char* src, int samples ) ; + +// silent frame functions +conf_frame* get_silent_frame( void ) ; +struct ast_frame* get_silent_slinear_frame( void ) ; + +// set delivery timestamp for frames +void set_conf_frame_delivery( conf_frame* frame, struct timeval time ) ; + +#endif diff --git a/apps/conference/member.c b/apps/conference/member.c new file mode 100644 index 0000000..d7f98af --- /dev/null +++ b/apps/conference/member.c @@ -0,0 +1,3332 @@ + +// $Id: member.c 885 2007-06-27 15:41:18Z sbalea $ + +/* + * app_conference + * + * A channel independent conference application for Asterisk + * + * Copyright (C) 2002, 2003 Junghanns.NET GmbH + * Copyright (C) 2003, 2004 HorizonLive.com, Inc. + * Copyright (C) 2005, 2006 HorizonWimba, Inc. + * Copyright (C) 2007 Wimba, Inc. + * + * Klaus-Peter Junghanns + * + * Video Conferencing support added by + * Neil Stratford + * Copyright (C) 2005, 2005 Vipadia Limited + * + * VAD driven video conferencing, text message support + * and miscellaneous enhancements added by + * Mihai Balea + * + * This program may be modified and distributed under the + * terms of the GNU General Public License. You should have received + * a copy of the GNU General Public License along with this + * program; if not, write to the Free Software Foundation, Inc. + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include +#include "asterisk/channel.h" +#include "asterisk/app.h" +#include "asterisk/autoconfig.h" +#include "member.h" + + +// process an incoming frame. Returns 0 normally, 1 if hangup was received. +static int process_incoming(struct ast_conf_member *member, struct ast_conference *conf, struct ast_frame *f) +{ + int silent_frame = 0; + struct ast_conf_member *src_member ; + + // In Asterisk 1.4 AST_FRAME_DTMF is equivalent to AST_FRAME_DTMF_END + if (f->frametype == AST_FRAME_DTMF) + { + if (member->dtmf_switch) + { + ast_mutex_lock( &member->lock ) ; + switch (f->subclass) { + case '0' :member->req_id=0; + break; + case '1' :member->req_id=1; + break; + case '2' :member->req_id=2; + break; + case '3' :member->req_id=3; + break; + case '4' :member->req_id=4; + break; + case '5' :member->req_id=5; + break; + case '6' :member->req_id=6; + break; + case '7' :member->req_id=7; + break; + case '8' :member->req_id=8; + break; + case '9' :member->req_id=9; + break; + case '*' : + if (member->mute_video == 0 && member->mute_audio == 0) + { + member->mute_video = 1; + member->mute_audio = 1; + } + else if (member->mute_video == 1 && member->mute_audio == 1) + { + member->mute_video = 0; + member->mute_audio = 0; + } + break; + } + member->conference = 1; // switch me + ast_mutex_unlock( &member->lock ) ; + } + if (member->dtmf_relay) + { + // output to manager... + manager_event( + EVENT_FLAG_CALL, + "ConferenceDTMF", + "ConferenceName: %s\r\n" + "Channel: %s\r\n" + "CallerID: %s\r\n" + "CallerIDName: %s\r\n" + "Key: %c\r\n", + conf->name, + member->channel_name, + member->chan->cid.cid_num ? member->chan->cid.cid_num : "unknown", + member->chan->cid.cid_name ? member->chan->cid.cid_name : "unknown", + f->subclass + ) ; + + } + if (!member->dtmf_switch && !member->dtmf_relay) + { + // relay this to the listening channels + queue_incoming_dtmf_frame( member, f ); + } + } else if (f->frametype == AST_FRAME_DTMF_BEGIN) + { + if (!member->dtmf_switch && !member->dtmf_relay) + { + // relay this to the listening channels + queue_incoming_dtmf_frame( member, f ); + } + } + + ast_mutex_lock( &member->lock ) ; + // Handle a local or remote conference + if (member->conference) + { + int req_id = member->req_id; + ast_mutex_unlock( &member->lock ); + // this will return NULL or a locked member + src_member = check_active_video(req_id,conf); + // Stream a picture to the recipient if no active video + if (!src_member) + { + // Mihai: we don't want to send video here, we cannot negotiate codec + // and we don't know what codec the conference is using + //if (member->norecv_video == 0) + //{ + // if(!ast_streamfile(member->chan,"novideo",member->chan->language)) + // { + // ast_waitstream(member->chan,""); + // } + //} + } + else + { + // Send a FIR to the new sender + ast_indicate(src_member->chan,AST_CONTROL_VIDUPDATE); + // we will have locked in check_active_video() + ast_mutex_unlock( &src_member->lock); + } + ast_mutex_lock( &member->lock ); + member->conference = 0; + } + ast_mutex_unlock( &member->lock ); + + + if ((f->frametype == AST_FRAME_VOICE && member->mute_audio == 1) || (f->frametype == AST_FRAME_VIDEO && member->mute_video == 1)) + { + // this is a listen-only user, ignore the frame + //ast_log( AST_CONF_DEBUG, "Listen only user frame"); + ast_frfree( f ) ; + f = NULL ; + } + else if ( f->frametype == AST_FRAME_VOICE ) + { //ast_log( AST_CONF_DEBUG, "Got voice frame"); + // reset silence detection flag + silent_frame = 0 ; + + // accounting: count the incoming frame + member->frames_in++ ; + +#if ( SILDET == 2 ) + // + // make sure we have a valid dsp and frame type + // + if ( + member->dsp != NULL + && f->subclass == AST_FORMAT_SLINEAR + && f->datalen == AST_CONF_FRAME_DATA_SIZE + ) + { + // send the frame to the preprocessor + int spx_ret; + spx_ret = speex_preprocess( member->dsp, f->data, NULL ); +#ifdef DEBUG_USE_TIMELOG + TIMELOG(spx_ret, 3, "speex_preprocess"); +#endif + if ( spx_ret == 0 ) + { + // + // we ignore the preprocessor's outcome if we've seen voice frames + // in within the last AST_CONF_SKIP_SPEEX_PREPROCESS frames + // + if ( member->ignore_speex_count > 0 ) + { + // ast_log( AST_CONF_DEBUG, "ignore_speex_count => %d\n", ignore_speex_count ) ; + + // skip speex_preprocess(), and decrement counter + --member->ignore_speex_count ; + } + else + { + // set silent_frame flag + silent_frame = 1 ; + } + } + else + { + // voice detected, reset skip count + member->ignore_speex_count = AST_CONF_SKIP_SPEEX_PREPROCESS ; + } + } +#endif + if ( !silent_frame ) + queue_incoming_frame( member, f ); + + // free the original frame + ast_frfree( f ) ; + f = NULL ; + + } + else if (f->frametype == AST_FRAME_VIDEO) + { + queue_incoming_video_frame( member, f ); + + // free the original frame + ast_frfree( f ) ; + f = NULL ; + + } + else if ( + f->frametype == AST_FRAME_CONTROL + && f->subclass == AST_CONTROL_HANGUP + ) + { + // hangup received + + // free the frame + ast_frfree( f ) ; + f = NULL ; + + // break out of the while ( 42 == 42 ) + return 1; + } + else if ( + f->frametype == AST_FRAME_CONTROL + && f->subclass == AST_CONTROL_VIDUPDATE + ) + { + // say we have switched to cause a FIR to + // be sent to the sender + ast_mutex_lock( &member->lock ) ; + member->conference = 1; + ast_mutex_unlock( &member->lock ) ; + + // free the original frame + ast_frfree( f ) ; + f = NULL ; + } + else if ( f->frametype == AST_FRAME_TEXT && member->does_text ) + { + if ( strncmp(f->data, AST_CONF_CONTROL_CAMERA_DISABLED, strlen(AST_CONF_CONTROL_CAMERA_DISABLED)) == 0 ) + { + ast_mutex_lock(&member->lock); + manager_event(EVENT_FLAG_CALL, + "ConferenceCameraDisabled", + "ConferenceName: %s\r\nChannel: %s\r\n", + conf->name, + member->channel_name); + member->no_camera = 1; + ast_mutex_unlock(&member->lock); + } else if ( strncmp(f->data, AST_CONF_CONTROL_CAMERA_ENABLED, strlen(AST_CONF_CONTROL_CAMERA_ENABLED)) == 0 ) + { + ast_mutex_lock(&member->lock); + manager_event(EVENT_FLAG_CALL, + "ConferenceCameraEnabled", + "ConferenceName: %s\r\nChannel: %s\r\n", + conf->name, + member->channel_name); + member->no_camera = 0; + ast_mutex_unlock(&member->lock); + } else if ( strncmp(f->data, AST_CONF_CONTROL_STOP_VIDEO_TRANSMIT, strlen(AST_CONF_CONTROL_STOP_VIDEO_TRANSMIT)) == 0 ) + { + ast_mutex_lock(&member->lock); + manager_event(EVENT_FLAG_CALL, + "ConferenceStopVideoTransmit", + "ConferenceName: %s\r\nChannel: %s\r\n", + conf->name, + member->channel_name); + member->norecv_video = 1; + ast_mutex_unlock(&member->lock); + } else if ( strncmp(f->data, AST_CONF_CONTROL_START_VIDEO_TRANSMIT, strlen(AST_CONF_CONTROL_START_VIDEO_TRANSMIT)) == 0 ) + { + ast_mutex_lock(&member->lock); + manager_event(EVENT_FLAG_CALL, + "ConferenceStartVideoTransmit", + "ConferenceName: %s\r\nChannel: %s\r\n", + conf->name, + member->channel_name); + member->norecv_video = 0; + ast_mutex_unlock(&member->lock); + } + ast_frfree(f); + f = NULL; + } else + { + // undesirables + ast_frfree( f ) ; + f = NULL ; + } + + return 0; +} + +// get the next frame from the soundq; must be called with member locked. +static struct ast_frame *get_next_soundframe(struct ast_conf_member *member, struct ast_frame + *exampleframe) { + struct ast_frame *f; + +again: + f=ast_readframe(member->soundq->stream); + + if(!f) { // we're done with this sound; remove it from the queue, and try again + struct ast_conf_soundq *toboot = member->soundq; + + ast_closestream(toboot->stream); + member->soundq = toboot->next; + + //ast_log( LOG_WARNING, "finished playing a sound, next = %x\n", member->soundq); + // notify applications via mgr interface that this sound has been played + manager_event( + EVENT_FLAG_CALL, + "ConferenceSoundComplete", + "Channel: %s\r\n" + "Sound: %s\r\n", + member->channel_name, + toboot->name + ); + + free(toboot); + if(member->soundq) goto again; + + // if we get here, we've gotten to the end of the queue; reset write format + if ( ast_set_write_format( member->chan, member->write_format ) < 0 ) + { + ast_log( LOG_ERROR, "unable to set write format to %d\n", + member->write_format ) ; + } + } else { + // copy delivery from exampleframe + f->delivery = exampleframe->delivery; + } + + return f; +} + + +// process outgoing frames for the channel, playing either normal conference audio, +// or requested sounds +static int process_outgoing(struct ast_conf_member *member) +{ + conf_frame* cf ; // frame read from the output queue + struct ast_frame *f; + struct ast_frame *realframe = NULL; + + for(;;) + { + // acquire member mutex and grab a frame. + ast_mutex_lock( &member->lock ) ; + cf = get_outgoing_frame( member ) ; + + // if there's no frames exit the loop. + if ( !cf ) + { + ast_mutex_unlock( &member->lock ) ; + break; + } + + + f = cf->fr; + + // if we're playing sounds, we can just replace the frame with the + // next sound frame, and send it instead + if ( member->soundq ) + { + realframe = f; + f = get_next_soundframe(member, f); + if ( !f ) + { + // if we didn't get anything, just revert to "normal" + f = realframe; + realframe = NULL; + } else + { + // We have a sound frame now, but we need to make sure it's the same + // format as our channel write format + int wf = member->chan->writeformat & AST_FORMAT_AUDIO_MASK; + if ( f->frametype == AST_FRAME_VOICE && !(wf & f->subclass) ) + { + // We need to change our channel's write format + ast_set_write_format(member->chan, f->subclass); + } + } + } + + ast_mutex_unlock(&member->lock); + + +#ifdef DEBUG_FRAME_TIMESTAMPS + // !!! TESTING !!! + int delivery_diff = usecdiff( &f->delivery, &member->lastsent_timeval ) ; + if ( delivery_diff != AST_CONF_FRAME_INTERVAL ) + { + ast_log( AST_CONF_DEBUG, "unanticipated delivery time, delivery_diff => %d, delivery.tv_usec => %ld\n", + delivery_diff, f->delivery.tv_usec ) ; + } + + // !!! TESTING !!! + if ( + f->delivery.tv_sec < member->lastsent_timeval.tv_sec + || ( + f->delivery.tv_sec == member->lastsent_timeval.tv_sec + && f->delivery.tv_usec <= member->lastsent_timeval.tv_usec + ) + ) + { + ast_log( LOG_WARNING, "queued frame timestamped in the past, %ld.%ld <= %ld.%ld\n", + f->delivery.tv_sec, f->delivery.tv_usec, + member->lastsent_timeval.tv_sec, member->lastsent_timeval.tv_usec ) ; + } + member->lastsent_timeval = f->delivery ; +#endif + +#ifdef DEBUG_USE_TIMELOG + TIMELOG( ast_write( member->chan, f ), 10, "member: ast_write"); +#else + + // send the voice frame + if ( ast_write( member->chan, f ) == 0 ) + { + struct timeval tv = ast_tvnow(); + ast_log( AST_CONF_DEBUG, "SENT VOICE FRAME, channel => %s, frames_out => %ld, s => %ld, ms => %ld\n", + member->channel_name, member->frames_out, tv.tv_sec, tv.tv_usec ) ; + } + else + { + // log 'dropped' outgoing frame + ast_log( LOG_ERROR, "unable to write voice frame to channel, channel => %s\n", member->channel_name ) ; + + // accounting: count dropped outgoing frames + member->frames_out_dropped++ ; + } +#endif + // clean up frame + delete_conf_frame( cf ) ; + + } + + // Do the same for video, suck it dry + for(;;) + { + // grab a frame. + cf = get_outgoing_video_frame( member ) ; + + // if there's no frames exit the loop. + if(!cf){ + break; + } + + f = cf->fr; + + // send the video frame + if ( ast_write_video( member->chan, f ) == 1 ) + { + struct timeval tv = ast_tvnow(); + ast_log( AST_CONF_DEBUG, "SENT VIDEO FRAME, channel => %s, frames_out => %ld, s => %ld, ms => %ld\n", + member->channel_name, member->frames_out, tv.tv_sec, tv.tv_usec ) ; + } + else + { + // log 'dropped' outgoing frame + ast_log( AST_CONF_DEBUG, "unable to write video frame to channel, channel => %s\n", member->channel_name ) ; + + // accounting: count dropped outgoing frames + member->video_frames_out_dropped++ ; + } + + // clean up frame + delete_conf_frame( cf ) ; + + } + + // Do the same for dtmf, suck it dry + for(;;) + { + // acquire member mutex and grab a frame. + cf = get_outgoing_dtmf_frame( member ) ; + + // if there's no frames exit the loop. + if(!cf) break; + + // send the dtmf frame + if ( ast_write( member->chan, cf->fr ) == 0 ) + { + struct timeval tv = ast_tvnow(); + ast_log( AST_CONF_DEBUG, "SENT DTMF FRAME, channel => %s, frames_out => %ld, s => %ld, ms => %ld\n", + member->channel_name, member->frames_out, tv.tv_sec, tv.tv_usec ) ; + + } + else + { + // log 'dropped' outgoing frame + ast_log( AST_CONF_DEBUG, "unable to write dtmf frame to channel, channel => %s\n", member->channel_name ) ; + + // accounting: count dropped outgoing frames + member->dtmf_frames_out_dropped++ ; + } + + // clean up frame + delete_conf_frame( cf ) ; + } + + // Do the same for text, hell, why not? + for(;;) + { + // acquire member mutex and grab a frame. + cf = get_outgoing_text_frame( member ) ; + + // if there's no frames exit the loop. + if(!cf) break; + + // send the text frame + if ( ast_write( member->chan, cf->fr ) == 0 ) + { + struct timeval tv = ast_tvnow(); + ast_log( AST_CONF_DEBUG, "SENT TEXT FRAME, channel => %s, frames_out => %ld, s => %ld, ms => %ld\n", + member->channel_name, member->frames_out, tv.tv_sec, tv.tv_usec ) ; + + } + else + { + // log 'dropped' outgoing frame + ast_log( AST_CONF_DEBUG, "unable to write text frame to channel, channel => %s\n", member->channel_name ) ; + + // accounting: count dropped outgoing frames + member->text_frames_out_dropped++ ; + } + + // clean up frame + delete_conf_frame( cf ) ; + } + + + return 0; +} + +static int member_checkkick( struct ast_conf_member *member ) +{ + int kick; + ast_mutex_lock( &member->lock ) ; + kick = member->kick_flag; + ast_mutex_unlock( &member->lock ) ; + return kick; +} + +// +// main member thread function +// + +int member_exec( struct ast_channel* chan, void* data ) +{ +// struct timeval start, end ; +// start = ast_tvnow(); + + struct ast_conference *conf ; + struct ast_conf_member *member ; + + struct ast_frame *f ; // frame received from ast_read() + + int left = 0 ; + int res; + + ast_log( AST_CONF_DEBUG, "Begin processing member thread, channel => %s\n", chan->name ) ; + + // + // If the call has not yet been answered, answer the call + // Note: asterisk apps seem to check _state, but it seems like it's safe + // to just call ast_answer. It will just do nothing if it is up. + // it will also return -1 if the channel is a zombie, or has hung up. + // + + res = ast_answer( chan ) ; + if ( res ) + { + ast_log( LOG_ERROR, "unable to answer call\n" ) ; + return -1 ; + } + + // + // create a new member for the conference + // + +// ast_log( AST_CONF_DEBUG, "creating new member, id => %s, flags => %s, p => %s\n", +// id, flags, priority ) ; + + member = create_member( chan, (const char*)( data ) ) ; // flags, atoi( priority ) ) ; + + // unable to create member, return an error + if ( member == NULL ) + { + ast_log( LOG_ERROR, "unable to create member\n" ) ; + return -1 ; + } + + // + // setup asterisk read/write formats + // +#if 0 + ast_log( AST_CONF_DEBUG, "CHANNEL INFO, CHANNEL => %s, DNID => %s, CALLER_ID => %s, ANI => %s\n", + chan->name, chan->dnid, chan->callerid, chan->ani ) ; + + ast_log( AST_CONF_DEBUG, "CHANNEL CODECS, CHANNEL => %s, NATIVE => %d, READ => %d, WRITE => %d\n", + chan->name, chan->nativeformats, member->read_format, member->write_format ) ; +#endif + if ( ast_set_read_format( chan, member->read_format ) < 0 ) + { + ast_log( LOG_ERROR, "unable to set read format to signed linear\n" ) ; + delete_member( member ) ; + return -1 ; + } + + if ( ast_set_write_format( chan, member->write_format ) < 0 ) // AST_FORMAT_SLINEAR, chan->nativeformats + { + ast_log( LOG_ERROR, "unable to set write format to signed linear\n" ) ; + delete_member( member ) ; + return -1 ; + } + + // + // setup a conference for the new member + // + + conf = start_conference( member ) ; + + if ( conf == NULL ) + { + ast_log( LOG_ERROR, "unable to setup member conference\n" ) ; + delete_member( member) ; + return -1 ; + } + + + manager_event( + EVENT_FLAG_CALL, + "ConferenceJoin", + "ConferenceName: %s\r\n" + "Member: %d\r\n" + "Channel: %s\r\n" + "CallerID: %s\r\n" + "CallerIDName: %s\r\n" + "Count: %d\r\n", + conf->name, + member->id, + member->channel_name, + member->chan->cid.cid_num ? member->chan->cid.cid_num : "unknown", + member->chan->cid.cid_name ? member->chan->cid.cid_name: "unknown", + conf->membercount + ) ; + + // Store the CID information + if ( member->chan->cid.cid_num ) + { + if ( (member->callerid = malloc(strlen(member->chan->cid.cid_num)+1)) ) + memcpy(member->callerid,member->chan->cid.cid_num, strlen(member->chan->cid.cid_num)+1); + } else + member->callerid = NULL; + + if ( member->chan->cid.cid_name ) + { + if ( (member->callername = malloc(strlen(member->chan->cid.cid_name)+1)) ) + memcpy(member->callername, member->chan->cid.cid_name, strlen(member->chan->cid.cid_name)+1); + } else + member->callername = NULL; + + + // + // process loop for new member ( this runs in it's own thread ) + // + + ast_log( AST_CONF_DEBUG, "begin member event loop, channel => %s\n", chan->name ) ; + + // timer timestamps + struct timeval base, curr ; + base = ast_tvnow(); + + // tell conference_exec we're ready for frames + member->ready_for_outgoing = 1 ; + + // notify others about enter + char * enter_snd = member->enter_snd; + if (strcmp(enter_snd, "-")!=0) + { + // lock conference + ast_mutex_lock( &conf->lock ); + + /*if (conf->membercount < 2) + { + enter_snd = "conf-onlyperson"; + }*/ + + struct ast_conf_member *membertest = conf->memberlist; + while (membertest != NULL) + { + // lock member for basic_play_sound + ast_mutex_lock(&membertest->lock); + + // basic_play_sound unlock member automatically. do not mute on enter message + if (!basic_play_sound ( membertest, enter_snd, 0 )) + { + ast_log(LOG_ERROR, "playing conference[%d] entry message <%s> FAILED on <%s>\n", conf->membercount, enter_snd, membertest->channel_name); + } + else + { + ast_log(LOG_NOTICE, "playing conference[%d] entry message <%s> on <%s>\n", conf->membercount, enter_snd, membertest->channel_name); + } + + membertest = membertest->next; + } + + // unlock conference + ast_mutex_unlock( &conf->lock ); + } + + while ( 42 == 42 ) + { + // make sure we have a channel to process + if ( chan == NULL ) + { + ast_log( LOG_NOTICE, "member channel has closed\n" ) ; + break ; + } + + //-----------------// + // INCOMING FRAMES // + //-----------------// + + // wait for an event on this channel + left = ast_waitfor( chan, AST_CONF_WAITFOR_LATENCY ) ; + + //ast_log( AST_CONF_DEBUG, "received event on channel, name => %s, left => %d\n", chan->name, left ) ; + + if ( left < 0 ) + { + // an error occured + ast_log( + LOG_NOTICE, + "an error occured waiting for a frame, channel => %s, error => %d\n", + chan->name, left + ) ; + break; // out of the 42==42 + } + else if ( left == 0 ) + { + // no frame has arrived yet + // ast_log( LOG_NOTICE, "no frame available from channel, channel => %s\n", chan->name ) ; + } + else if ( left > 0 ) + { + // a frame has come in before the latency timeout + // was reached, so we process the frame + + f = ast_read( chan ) ; + + if ( f == NULL ) + { + if (conf->debug_flag) + { + ast_log( LOG_NOTICE, "unable to read from channel, channel => %s\n", chan->name ) ; + // They probably want to hangup... + } + break ; + } + + // actually process the frame: break if we got hangup. + if(process_incoming(member, conf, f)) break; + + } + + if (member_checkkick(member)) break; + + //-----------------// + // OUTGOING FRAMES // + //-----------------// + + // update the current timestamps + curr = ast_tvnow(); + + process_outgoing(member); + // back to process incoming frames + continue ; + } + + ast_log( AST_CONF_DEBUG, "end member event loop, time_entered => %ld\n", member->time_entered.tv_sec ) ; + + // + // clean up + // + +#ifdef DEBUG_OUTPUT_PCM + // !!! TESTING !!! + if ( incoming_fh != NULL ) + fclose( incoming_fh ) ; +#endif + + // If we're driving another member, make sure its speaker count is correct + if ( member != NULL ) member->remove_flag = 1 ; + +// end = ast_tvnow(); +// int expected_frames = ( int )( floor( (double)( msecdiff( &end, &start ) / AST_CONF_FRAME_INTERVAL ) ) ) ; +// ast_log( AST_CONF_DEBUG, "expected_frames => %d\n", expected_frames ) ; + + return 0 ; +} + + + +struct ast_conf_member *check_active_video( int id, struct ast_conference *conf ) +{ + struct ast_conf_member *member; + + // acquire the conference lock + ast_mutex_lock( &conf->lock ) ; + + member = conf->memberlist; + while (member) + { + if (member->id == id) + { + // lock this member + ast_mutex_lock( &member->lock ) ; + ast_mutex_unlock( &conf->lock ) ; + return member; + } + member = member->next; + } + ast_mutex_unlock( &conf->lock ) ; + return NULL; +} + +// +// manange member functions +// + +struct ast_conf_member* create_member( struct ast_channel *chan, const char* data ) +{ + char *parse; + AST_DECLARE_APP_ARGS(args, + AST_APP_ARG(conf_name); + AST_APP_ARG(flags); + AST_APP_ARG(enter_snd); + AST_APP_ARG(leave_snd); + AST_APP_ARG(priority); + AST_APP_ARG(vad_prob_start); + AST_APP_ARG(vad_prob_continue); + ); + + // + // check input + // + + if ( chan == NULL ) + { + ast_log( LOG_ERROR, "unable to create member with null channel\n" ) ; + return NULL ; + } + + if ( chan->name == NULL ) + { + ast_log( LOG_ERROR, "unable to create member with null channel name\n" ) ; + return NULL ; + } + + if (ast_strlen_zero(data)) { + ast_log(LOG_ERROR, "Conference requires an argument (conf_name[|flags[|enter_snd[|leave_snd[|priority[vad_prob_start[|vad_prob_continue]]]]]]))\n"); + return NULL; + } + + // + // allocate memory for new conference member + // + + struct ast_conf_member *member = calloc( 1, sizeof( struct ast_conf_member ) ) ; + + if ( member == NULL ) + { + ast_log( LOG_ERROR, "unable to malloc ast_conf_member\n" ) ; + return NULL ; + } + + // initialize mutex + ast_mutex_init( &member->lock ) ; + + // + // initialize member with passed data values + // + + parse = ast_strdupa(data); + + AST_STANDARD_APP_ARGS(args, parse); + + // parse the id + if (ast_strlen_zero(args.conf_name)) + { + ast_log( LOG_ERROR, "unable to parse member id\n" ) ; + free( member ) ; + return NULL ; + } + else + member->conf_name = ast_strdup(args.conf_name); + + // parse the flags + if (!ast_strlen_zero(args.flags)) + member->flags = ast_strdup(args.flags); + else + member->flags = ast_strdup(""); + + // parse enter sound + if (!ast_strlen_zero(args.enter_snd)) + member->enter_snd = ast_strdup(args.enter_snd); + else + member->enter_snd = ast_strdup("enter"); + + // parse leave sound + if (!ast_strlen_zero(args.leave_snd)) + member->leave_snd = ast_strdup(args.leave_snd); + else + member->leave_snd = ast_strdup("leave"); + + if (!ast_strlen_zero(args.priority)) + // parse the priority + member->priority = atoi(args.priority); + else + member->priority = 0; + + if (!ast_strlen_zero(args.vad_prob_start)) + member->vad_prob_start = atof(args.vad_prob_start); + else + member->vad_prob_start = AST_CONF_PROB_START; + + if (!ast_strlen_zero(args.vad_prob_continue)) + member->vad_prob_continue = atof(args.vad_prob_continue); + else + member->vad_prob_continue = AST_CONF_PROB_CONTINUE; + + // debugging + ast_log( + AST_CONF_DEBUG, + "parsed data params, id => %s, flags => %s, priority => %d, vad_prob_start => %f, vad_prob_continue => %f\n", + member->conf_name, member->flags, member->priority, member->vad_prob_start, member->vad_prob_continue + ) ; + + // + // initialize member with default values + // + + // keep pointer to member's channel + member->chan = chan ; + + // copy the channel name + member->channel_name = malloc( strlen( chan->name ) + 1 ) ; + strcpy( member->channel_name, chan->name ) ; + + // ( default can be overridden by passed flags ) + member->mute_audio = 0; + member->mute_video = 0; + member->norecv_audio = 0; + member->norecv_video = 0; + member->no_camera = 0; + + // moderator? + member->ismoderator = 0; + + // ready flag + member->ready_for_outgoing = 0 ; + + // incoming frame queue + member->inFrames = NULL ; + member->inFramesTail = NULL ; + member->inFramesCount = 0 ; + + member->inVideoFrames = NULL ; + member->inVideoFramesTail = NULL ; + member->inVideoFramesCount = 0 ; + + member->inDTMFFrames = NULL ; + member->inDTMFFramesTail = NULL ; + member->inDTMFFramesCount = 0 ; + + member->inTextFrames = NULL ; + member->inTextFramesTail = NULL ; + member->inTextFramesCount = 0 ; + + member->conference = 1; // we have switched req_id + member->dtmf_switch = 0; // no dtmf switch by default + member->dtmf_relay = 0; // no dtmf relay by default + + // start of day video ids + member->req_id = -1; + member->id = -1; + + member->first_frame_received = 0; // cause a FIR after NAT delay + + // last frame caching + member->inFramesRepeatLast = 0 ; + member->inFramesLast = NULL ; + member->okayToCacheLast = 0 ; + + // outgoing frame queue + member->outFrames = NULL ; + member->outFramesTail = NULL ; + member->outFramesCount = 0 ; + + member->outVideoFrames = NULL ; + member->outVideoFramesTail = NULL ; + member->outVideoFramesCount = 0 ; + + member->outDTMFFrames = NULL ; + member->outDTMFFramesTail = NULL ; + member->outDTMFFramesCount = 0 ; + + member->outTextFrames = NULL ; + member->outTextFramesTail = NULL ; + member->outTextFramesCount = 0 ; + + // ( not currently used ) + // member->samplesperframe = AST_CONF_BLOCK_SAMPLES ; + + // used for determining need to mix frames + // and for management interface notification + // and for VAD based video switching + member->speaking_state_notify = 0 ; + member->speaking_state = 0 ; + member->local_speaking_state = 0; + member->speaker_count = 0; + member->driven_member = NULL; + + // linked-list pointer + member->next = NULL ; + + // account data + member->frames_in = 0 ; + member->frames_in_dropped = 0 ; + member->frames_out = 0 ; + member->frames_out_dropped = 0 ; + member->video_frames_in = 0 ; + member->video_frames_in_dropped = 0 ; + member->video_frames_out = 0 ; + member->video_frames_out_dropped = 0 ; + member->dtmf_frames_in = 0 ; + member->dtmf_frames_in_dropped = 0 ; + member->dtmf_frames_out = 0 ; + member->dtmf_frames_out_dropped = 0 ; + member->text_frames_in = 0 ; + member->text_frames_in_dropped = 0 ; + member->text_frames_out = 0 ; + member->text_frames_out_dropped = 0 ; + + // for counting sequentially dropped frames + member->sequential_drops = 0 ; + member->since_dropped = 0 ; + + // flags + member->remove_flag = 0 ; + member->kick_flag = 0; + + // record start time + // init dropped frame timestamps + // init state change timestamp + member->time_entered = + member->last_in_dropped = + member->last_out_dropped = + member->last_state_change = ast_tvnow(); + + // + // parse passed flags + // + + // silence detection flags w/ defaults + member->vad_flag = 0 ; + member->denoise_flag = 0 ; + member->agc_flag = 0 ; + + // is this member using the telephone? + member->via_telephone = 0 ; + + // temp pointer to flags string + char* flags = member->flags ; + + int i; + + for ( i = 0 ; i < strlen( flags ) ; ++i ) + { + + if (flags[i] >= (int)'0' && flags[i] <= (int)'9') + { + if (member->req_id < 0) + { + member->req_id = flags[i] - (int)'0'; + } + else + { + int newid = flags[i] - (int)'0'; + // need to boot anyone with this id already + // will happen in add_member + member->id = newid; + } + } + else + { + // allowed flags are C, c, L, l, V, D, A, C, X, R, T, t, M, S + // mute/no_recv options + switch ( flags[i] ) + { + case 'C': + member->mute_video = 1; + break ; + case 'c': + member->norecv_video = 1; + break ; + case 'L': + member->mute_audio = 1; + break ; + case 'l': + member->norecv_audio = 1; + break; + + // speex preprocessing options + case 'V': + member->vad_flag = 1 ; + break ; + case 'D': + member->denoise_flag = 1 ; + break ; + case 'A': + member->agc_flag = 1 ; + break ; + + // dtmf/moderator/video switching options + case 'X': + member->dtmf_switch = 1; + break; + case 'R': + member->dtmf_relay = 1; + break; + case 'S': + member->vad_switch = 1; + break; + case 'M': + member->ismoderator = 1; + break; + case 'N': + member->no_camera = 1; + break; + case 't': + member->does_text = 1; + break; + + //Telephone connection + case 'T': + member->via_telephone = 1; + break; + + default: + ast_log( LOG_WARNING, "received invalid flag, chan => %s, flag => %c\n", + chan->name, flags[i] ); + break ; + } + } + } + + // set the dsp to null so silence detection is disabled by default + member->dsp = NULL ; + +#if ( SILDET == 2 ) + // + // configure silence detection and preprocessing + // if the user is coming in via the telephone, + // and is not listen-only + // + if ( + member->via_telephone == 1 + && member->type != 'L' + ) + { + // create a speex preprocessor + member->dsp = speex_preprocess_state_init( AST_CONF_BLOCK_SAMPLES, AST_CONF_SAMPLE_RATE ) ; + + if ( member->dsp == NULL ) + { + ast_log( LOG_WARNING, "unable to initialize member dsp, channel => %s\n", chan->name ) ; + } + else + { + ast_log( LOG_NOTICE, "member dsp initialized, channel => %s, v => %d, d => %d, a => %d\n", + chan->name, member->vad_flag, member->denoise_flag, member->agc_flag ) ; + + // set speex preprocessor options + speex_preprocess_ctl( member->dsp, SPEEX_PREPROCESS_SET_VAD, &(member->vad_flag) ) ; + speex_preprocess_ctl( member->dsp, SPEEX_PREPROCESS_SET_DENOISE, &(member->denoise_flag) ) ; + speex_preprocess_ctl( member->dsp, SPEEX_PREPROCESS_SET_AGC, &(member->agc_flag) ) ; + + speex_preprocess_ctl( member->dsp, SPEEX_PREPROCESS_SET_PROB_START, &member->vad_prob_start ) ; + speex_preprocess_ctl( member->dsp, SPEEX_PREPROCESS_SET_PROB_CONTINUE, &member->vad_prob_continue ) ; + + speex_preprocess_ctl( member->dsp, SPEEX_PREPROCESS_GET_PROB_START, &member->vad_prob_start ) ; + speex_preprocess_ctl( member->dsp, SPEEX_PREPROCESS_GET_PROB_CONTINUE, &member->vad_prob_continue ) ; + + ast_log( AST_CONF_DEBUG, "speech_prob_start => %f, speech_prob_continue => %f\n", + member->vad_prob_start, member->vad_prob_continue ) ; + } + } +#endif + + // + // set connection type + // + + if ( member->via_telephone == 1 ) + { + member->connection_type = 'T' ; + } + else if ( strncmp( member->channel_name, "SIP", 3 ) == 0 ) + { + member->connection_type = 'S' ; + } + else // default to iax + { + member->connection_type = 'X' ; + } + + // + // read, write, and translation options + // + + // set member's audio formats, taking dsp preprocessing into account + // ( chan->nativeformats, AST_FORMAT_SLINEAR, AST_FORMAT_ULAW, AST_FORMAT_GSM ) + member->read_format = ( member->dsp == NULL ) ? chan->nativeformats : AST_FORMAT_SLINEAR ; + + member->write_format = chan->nativeformats; + + // 1.2 or 1.3+ +#ifdef AST_FORMAT_AUDIO_MASK + + member->read_format &= AST_FORMAT_AUDIO_MASK; + member->write_format &= AST_FORMAT_AUDIO_MASK; +#endif + + // translation paths ( ast_translator_build_path() returns null if formats match ) + member->to_slinear = ast_translator_build_path( AST_FORMAT_SLINEAR, member->read_format ) ; + member->from_slinear = ast_translator_build_path( member->write_format, AST_FORMAT_SLINEAR ) ; + + ast_log( AST_CONF_DEBUG, "AST_FORMAT_SLINEAR => %d\n", AST_FORMAT_SLINEAR ) ; + + // index for converted_frames array + switch ( member->write_format ) + { + case AST_FORMAT_SLINEAR: + member->write_format_index = AC_SLINEAR_INDEX ; + break ; + + case AST_FORMAT_ULAW: + member->write_format_index = AC_ULAW_INDEX ; + break ; + + case AST_FORMAT_ALAW: + member->write_format_index = AC_ALAW_INDEX ; + break ; + + case AST_FORMAT_GSM: + member->write_format_index = AC_GSM_INDEX ; + break ; + + case AST_FORMAT_SPEEX: + member->write_format_index = AC_SPEEX_INDEX; + break; + +#ifdef AC_USE_G729A + case AST_FORMAT_G729A: + member->write_format_index = AC_G729A_INDEX; + break; +#endif + + default: + member->write_format_index = 0 ; + } + + // index for converted_frames array + switch ( member->read_format ) + { + case AST_FORMAT_SLINEAR: + member->read_format_index = AC_SLINEAR_INDEX ; + break ; + + case AST_FORMAT_ULAW: + member->read_format_index = AC_ULAW_INDEX ; + break ; + + case AST_FORMAT_ALAW: + member->read_format_index = AC_ALAW_INDEX ; + break ; + + case AST_FORMAT_GSM: + member->read_format_index = AC_GSM_INDEX ; + break ; + + case AST_FORMAT_SPEEX: + member->read_format_index = AC_SPEEX_INDEX; + break; + +#ifdef AC_USE_G729A + case AST_FORMAT_G729A: + member->read_format_index = AC_G729A_INDEX; + break; +#endif + + default: + member->read_format_index = 0 ; + } + + // smoother defaults. + member->smooth_multiple =1; + member->smooth_size_in = -1; + member->smooth_size_out = -1; + member->inSmoother= NULL; + member->outPacker= NULL; + + switch (member->read_format){ + /* these assumptions may be incorrect */ + case AST_FORMAT_ULAW: + case AST_FORMAT_ALAW: + member->smooth_size_in = 160; //bytes + member->smooth_size_out = 160; //samples + break; + case AST_FORMAT_GSM: + /* + member->smooth_size_in = 33; //bytes + member->smooth_size_out = 160;//samples + */ + break; + case AST_FORMAT_SPEEX: + case AST_FORMAT_G729A: + /* this assumptions are wrong + member->smooth_multiple = 2 ; // for testing, force to dual frame + member->smooth_size_in = 39; // bytes + member->smooth_size_out = 160; // samples + */ + break; + case AST_FORMAT_SLINEAR: + member->smooth_size_in = 320; //bytes + member->smooth_size_out = 160; //samples + break; + default: + member->inSmoother = NULL; //don't use smoother for this type. + //ast_log( AST_CONF_DEBUG, "smoother is NULL for member->read_format => %d\n", member->read_format); + } + + if (member->smooth_size_in > 0){ + member->inSmoother = ast_smoother_new(member->smooth_size_in); + ast_log( AST_CONF_DEBUG, "created smoother(%d) for %d\n", member->smooth_size_in , member->read_format); + } + + // + // finish up + // + + ast_log( AST_CONF_DEBUG, "created member, type => %c, priority => %d, readformat => %d\n", + member->type, member->priority, chan->readformat ) ; + + return member ; +} + +struct ast_conf_member* delete_member( struct ast_conf_member* member ) +{ + // !!! NO RETURN TEST !!! + // do { sleep(1) ; } while (1) ; + + // !!! CRASH TEST !!! + // *((int *)0) = 0; + + if ( member == NULL ) + { + ast_log( LOG_WARNING, "unable to the delete null member\n" ) ; + return NULL ; + } + + ast_mutex_lock(&member->lock); + + // If member is driving another member, make sure its speaker count is correct + if ( member->driven_member != NULL && member->speaking_state == 1 ) + decrement_speaker_count(member->driven_member, 1); + + // + // clean up member flags + // + + if ( member->flags != NULL ) + { + // !!! DEBUGING !!! + ast_log( AST_CONF_DEBUG, "freeing member flags, name => %s\n", + member->channel_name ) ; + free( member->flags ) ; + } + + // + // delete the members frames + // + + conf_frame* cf ; + + // !!! DEBUGING !!! + ast_log( AST_CONF_DEBUG, "deleting member input frames, name => %s\n", + member->channel_name ) ; + + // incoming frames + cf = member->inFrames ; + + while ( cf != NULL ) + { + cf = delete_conf_frame( cf ) ; + } + + if (member->inSmoother != NULL) + ast_smoother_free(member->inSmoother); + + cf = member->inVideoFrames ; + + while ( cf != NULL ) + { + cf = delete_conf_frame( cf ) ; + } + + // !!! DEBUGING !!! + ast_log( AST_CONF_DEBUG, "deleting member output frames, name => %s\n", + member->channel_name ) ; + + // outgoing frames + cf = member->outFrames ; + + while ( cf != NULL ) + { + cf = delete_conf_frame( cf ) ; + } + + cf = member->outVideoFrames ; + + while ( cf != NULL ) + { + cf = delete_conf_frame( cf ) ; + } + +#if ( SILDET == 2 ) + if ( member->dsp != NULL ) + { + // !!! DEBUGING !!! + ast_log( AST_CONF_DEBUG, "destroying member preprocessor, name => %s\n", + member->channel_name ) ; + speex_preprocess_state_destroy( member->dsp ) ; + } +#endif + + // !!! DEBUGING !!! + ast_log( AST_CONF_DEBUG, "freeing member translator paths, name => %s\n", + member->channel_name ) ; + + // free the mixing translators + ast_translator_free_path( member->to_slinear ) ; + ast_translator_free_path( member->from_slinear ) ; + + // get a pointer to the next + // member so we can return it + struct ast_conf_member* nm = member->next ; + + ast_mutex_unlock(&member->lock); + + // !!! DEBUGING !!! + ast_log( AST_CONF_DEBUG, "freeing member channel name, name => %s\n", + member->channel_name ) ; + + // free the member's copy for the channel name + free( member->channel_name ) ; + + // free the member's copy of the conference name + free(member->conf_name); + + // !!! DEBUGING !!! + ast_log( AST_CONF_DEBUG, "freeing member\n" ) ; + + // free the member's memory + free(member->callerid); + free(member->callername); + + // free enter/leave sounds + free(member->enter_snd); + free(member->leave_snd); + + free( member ) ; + member = NULL ; + + return nm ; +} + +// +// incoming frame functions +// + +conf_frame* get_incoming_video_frame( struct ast_conf_member *member ) +{ + if ( member == NULL ) + { + ast_log( LOG_WARNING, "unable to get frame from null member\n" ) ; + return NULL ; + } + + ast_mutex_lock(&member->lock); + + if ( member->inVideoFramesCount == 0 ) + { + ast_mutex_unlock(&member->lock); + return NULL ; + } + + // + // return the next frame in the queue + // + + conf_frame* cfr = NULL ; + + // get first frame in line + cfr = member->inVideoFramesTail ; + + // if it's the only frame, reset the queue, + // else, move the second frame to the front + if ( member->inVideoFramesTail == member->inVideoFrames ) + { + member->inVideoFramesTail = NULL ; + member->inVideoFrames = NULL ; + } + else + { + // move the pointer to the next frame + member->inVideoFramesTail = member->inVideoFramesTail->prev ; + + // reset it's 'next' pointer + if ( member->inVideoFramesTail != NULL ) + member->inVideoFramesTail->next = NULL ; + } + + // separate the conf frame from the list + cfr->next = NULL ; + cfr->prev = NULL ; + + // decrement frame count + member->inVideoFramesCount-- ; + + ast_mutex_unlock(&member->lock); + return cfr ; + +} +conf_frame* get_incoming_dtmf_frame( struct ast_conf_member *member ) +{ + if ( member == NULL ) + { + ast_log( LOG_WARNING, "unable to get frame from null member\n" ) ; + return NULL ; + } + + ast_mutex_lock(&member->lock); + + if ( member->inDTMFFramesCount == 0 ) + { + ast_mutex_unlock(&member->lock); + return NULL ; + } + + // + // return the next frame in the queue + // + + conf_frame* cfr = NULL ; + + // get first frame in line + cfr = member->inDTMFFramesTail ; + + // if it's the only frame, reset the queue, + // else, move the second frame to the front + if ( member->inDTMFFramesTail == member->inDTMFFrames ) + { + member->inDTMFFramesTail = NULL ; + member->inDTMFFrames = NULL ; + } + else + { + // move the pointer to the next frame + member->inDTMFFramesTail = member->inDTMFFramesTail->prev ; + + // reset it's 'next' pointer + if ( member->inDTMFFramesTail != NULL ) + member->inDTMFFramesTail->next = NULL ; + } + + // separate the conf frame from the list + cfr->next = NULL ; + cfr->prev = NULL ; + + // decriment frame count + member->inDTMFFramesCount-- ; + + ast_mutex_unlock(&member->lock); + return cfr ; + +} + + +conf_frame* get_incoming_frame( struct ast_conf_member *member ) +{ + conf_frame *cf_result; + // + // sanity checks + // + + if ( member == NULL ) + { + ast_log( LOG_WARNING, "unable to get frame from null member\n" ) ; + return NULL ; + } + + ast_mutex_lock(&member->lock); + + // + // repeat last frame a couple times to smooth transition + // + +#ifdef AST_CONF_CACHE_LAST_FRAME + if ( member->inFramesCount == 0 ) + { + // nothing to do if there's no cached frame + if ( member->inFramesLast == NULL ) { + ast_mutex_unlock(&member->lock); + return NULL ; + } + + // turn off 'okay to cache' flag + member->okayToCacheLast = 0 ; + + if ( member->inFramesRepeatLast >= AST_CONF_CACHE_LAST_FRAME ) + { + // already used this frame AST_CONF_CACHE_LAST_FRAME times + + // reset repeat count + member->inFramesRepeatLast = 0 ; + + // clear the cached frame + delete_conf_frame( member->inFramesLast ) ; + member->inFramesLast = NULL ; + + // return null + ast_mutex_unlock(&member->lock); + return NULL ; + } + else + { + ast_log( AST_CONF_DEBUG, "repeating cached frame, channel => %s, inFramesRepeatLast => %d\n", + member->channel_name, member->inFramesRepeatLast ) ; + + // increment counter + member->inFramesRepeatLast++ ; + + // return a copy of the cached frame + cf_result = copy_conf_frame( member->inFramesLast ) ; + ast_mutex_unlock(&member->lock); + return cf_result; + } + } + else if ( member->okayToCacheLast == 0 && member->inFramesCount >= 3 ) + { + ast_log( AST_CONF_DEBUG, "enabling cached frame, channel => %s, incoming => %d, outgoing => %d\n", + member->channel_name, member->inFramesCount, member->outFramesCount ) ; + + // turn on 'okay to cache' flag + member->okayToCacheLast = 1 ; + } +#else + if ( member->inFramesCount == 0 ) { + ast_mutex_unlock(&member->lock); + return NULL ; + } +#endif // AST_CONF_CACHE_LAST_FRAME + + // + // return the next frame in the queue + // + + conf_frame* cfr = NULL ; + + // get first frame in line + cfr = member->inFramesTail ; + + // if it's the only frame, reset the queue, + // else, move the second frame to the front + if ( member->inFramesTail == member->inFrames ) + { + member->inFramesTail = NULL ; + member->inFrames = NULL ; + } + else + { + // move the pointer to the next frame + member->inFramesTail = member->inFramesTail->prev ; + + // reset it's 'next' pointer + if ( member->inFramesTail != NULL ) + member->inFramesTail->next = NULL ; + } + + // separate the conf frame from the list + cfr->next = NULL ; + cfr->prev = NULL ; + + // decriment frame count + member->inFramesCount-- ; + +#ifdef AST_CONF_CACHE_LAST_FRAME + // copy frame if queue is now empty + if ( + member->inFramesCount == 0 + && member->okayToCacheLast == 1 + ) + { + // reset repeat count + member->inFramesRepeatLast = 0 ; + + // clear cached frame + if ( member->inFramesLast != NULL ) + { + delete_conf_frame( member->inFramesLast ) ; + member->inFramesLast = NULL ; + } + + // cache new frame + member->inFramesLast = copy_conf_frame( cfr ) ; + } +#endif // AST_CONF_CACHE_LAST_FRAME + + ast_mutex_unlock(&member->lock); + return cfr ; +} + +int queue_incoming_video_frame( struct ast_conf_member* member, const struct ast_frame* fr ) +{ + // check on frame + if ( fr == NULL ) + { + ast_log( LOG_ERROR, "unable to queue null frame\n" ) ; + return -1 ; + } + + // check on member + if ( member == NULL ) + { + ast_log( LOG_ERROR, "unable to queue frame for null member\n" ) ; + return -1 ; + } + + // lock the member + ast_mutex_lock(&member->lock); + + if (!member->first_frame_received) + { + // nat=yes will be correct now + member->first_frame_received = 1; + member->conference = 1; + } + + // We have to drop if the queue is full! + if ( member->inVideoFramesCount >= AST_CONF_MAX_VIDEO_QUEUE ) + { + ast_log( + AST_CONF_DEBUG, + "unable to queue incoming VIDEO frame, channel => %s, incoming => %d, outgoing => %d\n", + member->channel_name, member->inVideoFramesCount, member->outVideoFramesCount + ) ; + ast_mutex_unlock(&member->lock); + return -1 ; + } + + // + // create new conf frame from passed data frame + // + + // ( member->inFrames may be null at this point ) + conf_frame* cfr = create_conf_frame( member, member->inVideoFrames, fr ) ; + + if ( cfr == NULL ) + { + ast_log( LOG_ERROR, "unable to malloc conf_frame\n" ) ; + ast_mutex_unlock(&member->lock); + return -1 ; + } + + // copy frame data pointer to conf frame + // cfr->fr = fr ; + + // + // add new frame to speaking members incoming frame queue + // ( i.e. save this frame data, so we can distribute it in conference_exec later ) + // + + if ( member->inVideoFrames == NULL ) + { + // this is the first frame in the buffer + member->inVideoFramesTail = cfr ; + member->inVideoFrames = cfr ; + } + else + { + // put the new frame at the head of the list + member->inVideoFrames = cfr ; + } + + // increment member frame count + member->inVideoFramesCount++ ; + + ast_mutex_unlock(&member->lock); + + // Everything has gone okay! + return 0; +} + +int queue_incoming_dtmf_frame( struct ast_conf_member* member, const struct ast_frame* fr ) +{ + //ast_log( AST_CONF_DEBUG, "queue incoming video frame\n"); + + // check on frame + if ( fr == NULL ) + { + ast_log( LOG_ERROR, "unable to queue null frame\n" ) ; + return -1 ; + } + + // check on member + if ( member == NULL ) + { + ast_log( LOG_ERROR, "unable to queue frame for null member\n" ) ; + return -1 ; + } + + ast_mutex_lock(&member->lock); + + // We have to drop if the queue is full! + if ( member->inDTMFFramesCount >= AST_CONF_MAX_DTMF_QUEUE ) + { + ast_log( + AST_CONF_DEBUG, + "unable to queue incoming DTMF frame, channel => %s, incoming => %d, outgoing => %d\n", + member->channel_name, member->inDTMFFramesCount, member->outDTMFFramesCount + ) ; + ast_mutex_unlock(&member->lock); + return -1 ; + } + + // + // create new conf frame from passed data frame + // + + // ( member->inFrames may be null at this point ) + conf_frame* cfr = create_conf_frame( member, member->inDTMFFrames, fr ) ; + + if ( cfr == NULL ) + { + ast_log( LOG_ERROR, "unable to malloc conf_frame\n" ) ; + ast_mutex_unlock(&member->lock); + return -1 ; + } + + // copy frame data pointer to conf frame + // cfr->fr = fr ; + + // + // add new frame to speaking members incoming frame queue + // ( i.e. save this frame data, so we can distribute it in conference_exec later ) + // + + if ( member->inDTMFFrames == NULL ) + { + // this is the first frame in the buffer + member->inDTMFFramesTail = cfr ; + member->inDTMFFrames = cfr ; + } + else + { + // put the new frame at the head of the list + member->inDTMFFrames = cfr ; + } + + // increment member frame count + member->inDTMFFramesCount++ ; + + ast_mutex_unlock(&member->lock); + + // Everything has gone okay! + return 0; +} + +int queue_incoming_frame( struct ast_conf_member* member, struct ast_frame* fr ) +{ + // + // sanity checks + // + + // check on frame + if ( fr == NULL ) + { + ast_log( LOG_ERROR, "unable to queue null frame\n" ) ; + return -1 ; + } + + // check on member + if ( member == NULL ) + { + ast_log( LOG_ERROR, "unable to queue frame for null member\n" ) ; + return -1 ; + } + + ast_mutex_lock(&member->lock); + + if ( member->inFramesCount > member->inFramesNeeded ) + { + if ( member->inFramesCount > AST_CONF_QUEUE_DROP_THRESHOLD ) + { + struct timeval curr = ast_tvnow(); + + // time since last dropped frame + long diff = ast_tvdiff_ms(curr, member->last_in_dropped); + + // number of milliseconds which must pass between frame drops + // ( 15 frames => -100ms, 10 frames => 400ms, 5 frames => 900ms, 0 frames => 1400ms, etc. ) + long time_limit = 1000 - ( ( member->inFramesCount - AST_CONF_QUEUE_DROP_THRESHOLD ) * 100 ) ; + + if ( diff >= time_limit ) + { + // count sequential drops + member->sequential_drops++ ; + + ast_log( + AST_CONF_DEBUG, + "dropping frame from input buffer, channel => %s, incoming => %d, outgoing => %d\n", + member->channel_name, member->inFramesCount, member->outFramesCount + ) ; + + // accounting: count dropped incoming frames + member->frames_in_dropped++ ; + + // reset frames since dropped + member->since_dropped = 0 ; + + // delete the frame + delete_conf_frame( get_incoming_frame( member ) ) ; + + member->last_in_dropped = ast_tvnow(); + } + else + { +/* + ast_log( + AST_CONF_DEBUG, + "input buffer larger than drop threshold, channel => %s, incoming => %d, outgoing => %d\n", + member->channel_name, member->inFramesCount, member->outFramesCount + ) ; +*/ + } + } + } + + // + // if we have to drop frames, we'll drop new frames + // because it's easier ( and doesn't matter much anyway ). + // + + if ( member->inFramesCount >= AST_CONF_MAX_QUEUE ) + { + // count sequential drops + member->sequential_drops++ ; + + ast_log( + AST_CONF_DEBUG, + "unable to queue incoming frame, channel => %s, incoming => %d, outgoing => %d\n", + member->channel_name, member->inFramesCount, member->outFramesCount + ) ; + + // accounting: count dropped incoming frames + member->frames_in_dropped++ ; + + // reset frames since dropped + member->since_dropped = 0 ; + + ast_mutex_unlock(&member->lock); + return -1 ; + } + + // reset sequential drops + member->sequential_drops = 0 ; + + // increment frames since dropped + member->since_dropped++ ; + + // + // create new conf frame from passed data frame + // + + // ( member->inFrames may be null at this point ) + if (member->inSmoother == NULL ){ + conf_frame* cfr = create_conf_frame( member, member->inFrames, fr ) ; + if ( cfr == NULL ) + { + ast_log( LOG_ERROR, "unable to malloc conf_frame\n" ) ; + ast_mutex_unlock(&member->lock); + return -1 ; + } + + // + // add new frame to speaking members incoming frame queue + // ( i.e. save this frame data, so we can distribute it in conference_exec later ) + // + + if ( member->inFrames == NULL ) { + member->inFramesTail = cfr ; + } + member->inFrames = cfr ; + member->inFramesCount++ ; + } else { + //feed frame(fr) into the smoother + + // smoother tmp frame + struct ast_frame *sfr; + int multiple = 1; + int i=0; + +#if 0 + if ( (member->smooth_size_in > 0 ) && (member->smooth_size_in * member->smooth_multiple != fr->datalen) ) + { + ast_log( AST_CONF_DEBUG, "resetting smooth_size_in. old size=> %d, multiple =>%d, datalen=> %d\n", member->smooth_size_in, member->smooth_multiple, fr->datalen ); + if ( fr->datalen % member->smooth_multiple != 0) { + // if datalen not divisible by smooth_multiple, assume we're just getting normal encoding. + // ast_log(AST_CONF_DEBUG,"smooth_multiple does not divide datalen. changing smooth size from %d to %d, multiple => 1\n", member->smooth_size_in, fr->datalen); + member->smooth_size_in = fr->datalen; + member->smooth_multiple = 1; + } else { + // assume a fixed multiple, so divide into datalen. + int newsmooth = fr->datalen / member->smooth_multiple ; + // ast_log(AST_CONF_DEBUG,"datalen is divisible by smooth_multiple, changing smooth size from %d to %d\n", member->smooth_size_in, newsmooth); + member->smooth_size_in = newsmooth; + } + + //free input smoother. + if (member->inSmoother != NULL) + ast_smoother_free(member->inSmoother); + + //make new input smoother. + member->inSmoother = ast_smoother_new(member->smooth_size_in); + } +#endif + + ast_smoother_feed( member->inSmoother, fr ); +ast_log (AST_CONF_DEBUG, "SMOOTH:Feeding frame into inSmoother, timestamp => %ld.%ld\n", fr->delivery.tv_sec, fr->delivery.tv_usec); + + if ( multiple > 1 ) + fr->samples /= multiple; + + // read smoothed version of frames, add to queue + while( ( sfr = ast_smoother_read( member->inSmoother ) ) ){ + + ++i; +ast_log( AST_CONF_DEBUG , "\treading new frame [%d] from smoother, inFramesCount[%d], \n\tsfr->frametype -> %d , sfr->subclass -> %d , sfr->datalen => %d sfr->samples => %d\n", i , member->inFramesCount , sfr->frametype, sfr->subclass, sfr->datalen, sfr->samples); +ast_log (AST_CONF_DEBUG, "SMOOTH:Reading frame from inSmoother, i=>%d, timestamp => %ld.%ld\n",i, sfr->delivery.tv_sec, sfr->delivery.tv_usec); + conf_frame* cfr = create_conf_frame( member, member->inFrames, sfr ) ; + if ( cfr == NULL ) + { + ast_log( LOG_ERROR, "unable to malloc conf_frame\n" ) ; + ast_mutex_unlock(&member->lock); + return -1 ; + } + + // + // add new frame to speaking members incoming frame queue + // ( i.e. save this frame data, so we can distribute it in conference_exec later ) + // + + if ( member->inFrames == NULL ) { + member->inFramesTail = cfr ; + } + member->inFrames = cfr ; + member->inFramesCount++ ; + } + } + ast_mutex_unlock(&member->lock); + return 0 ; +} + +// +// outgoing frame functions +// + +conf_frame* get_outgoing_frame( struct ast_conf_member *member ) +{ + if ( member == NULL ) + { + ast_log( LOG_WARNING, "unable to get frame from null member\n" ) ; + return NULL ; + } + + conf_frame* cfr ; + + // ast_log( AST_CONF_DEBUG, "getting member frames, count => %d\n", member->outFramesCount ) ; + + ast_mutex_lock(&member->lock); + + if ( member->outFramesCount > AST_CONF_MIN_QUEUE ) + { + cfr = member->outFramesTail ; + + // if it's the only frame, reset the queu, + // else, move the second frame to the front + if ( member->outFramesTail == member->outFrames ) + { + member->outFrames = NULL ; + member->outFramesTail = NULL ; + } + else + { + // move the pointer to the next frame + member->outFramesTail = member->outFramesTail->prev ; + + // reset it's 'next' pointer + if ( member->outFramesTail != NULL ) + member->outFramesTail->next = NULL ; + } + + // separate the conf frame from the list + cfr->next = NULL ; + cfr->prev = NULL ; + + // decriment frame count + member->outFramesCount-- ; + ast_mutex_unlock(&member->lock); + return cfr ; + } + ast_mutex_unlock(&member->lock); + return NULL ; +} + +int __queue_outgoing_frame( struct ast_conf_member* member, const struct ast_frame* fr, struct timeval delivery ) +{ + // accounting: count the number of outgoing frames for this member + member->frames_out++ ; + + // + // we have to drop frames, so we'll drop new frames + // because it's easier ( and doesn't matter much anyway ). + // + if ( member->outFramesCount >= AST_CONF_MAX_QUEUE ) + { + ast_log( + AST_CONF_DEBUG, + "unable to queue outgoing frame, channel => %s, incoming => %d, outgoing => %d\n", + member->channel_name, member->inFramesCount, member->outFramesCount + ) ; + + // accounting: count dropped outgoing frames + member->frames_out_dropped++ ; + return -1 ; + } + + // + // create new conf frame from passed data frame + // + + conf_frame* cfr = create_conf_frame( member, member->outFrames, fr ) ; + + if ( cfr == NULL ) + { + ast_log( LOG_ERROR, "unable to create new conf frame\n" ) ; + + // accounting: count dropped outgoing frames + member->frames_out_dropped++ ; + return -1 ; + } + + // set delivery timestamp + cfr->fr->delivery = delivery ; + + // + // add new frame to speaking members incoming frame queue + // ( i.e. save this frame data, so we can distribute it in conference_exec later ) + // + + if ( member->outFrames == NULL ) { + member->outFramesTail = cfr ; + } + member->outFrames = cfr ; + member->outFramesCount++ ; + + // return success + return 0 ; +} + +int queue_outgoing_frame( struct ast_conf_member* member, const struct ast_frame* fr, struct timeval delivery ) +{ + // check on frame + if ( fr == NULL ) + { + ast_log( LOG_ERROR, "unable to queue null frame\n" ) ; + return -1 ; + } + + // check on member + if ( member == NULL ) + { + ast_log( LOG_ERROR, "unable to queue frame for null member\n" ) ; + return -1 ; + } + + if ( ( member->outPacker == NULL ) && ( member->smooth_multiple > 1 ) && ( member->smooth_size_out > 0 ) ){ + //ast_log (AST_CONF_DEBUG, "creating outPacker with size => %d \n\t( multiple => %d ) * ( size => %d )\n", member->smooth_multiple * member-> smooth_size_out, member->smooth_multiple , member->smooth_size_out); + member->outPacker = ast_packer_new( member->smooth_multiple * member->smooth_size_out); + } + + if (member->outPacker == NULL ){ + return __queue_outgoing_frame( member, fr, delivery ) ; + } + else + { + struct ast_frame *sfr; + int exitval = 0; +//ast_log (AST_CONF_DEBUG, "sending fr into outPacker, datalen=>%d, samples=>%d\n",fr->datalen, fr->samples); + ast_packer_feed( member->outPacker , fr ); + while( (sfr = ast_packer_read( member->outPacker ) ) ) + { +//ast_log (AST_CONF_DEBUG, "read sfr from outPacker, datalen=>%d, samples=>%d\n",sfr->datalen, sfr->samples); + if ( __queue_outgoing_frame( member, sfr, delivery ) == -1 ) { + exitval = -1; + } + } + + return exitval; + } +} + +// +// outgoing frame functions +// + +conf_frame* get_outgoing_video_frame( struct ast_conf_member *member ) +{ + if ( member == NULL ) + { + ast_log( LOG_WARNING, "unable to get frame from null member\n" ) ; + return NULL ; + } + + conf_frame* cfr ; + + ast_mutex_lock(&member->lock); + + // ast_log( AST_CONF_DEBUG, "getting member frames, count => %d\n", member->outFramesCount ) ; + + if ( member->outVideoFramesCount > AST_CONF_MIN_QUEUE ) + { + cfr = member->outVideoFramesTail ; + + // if it's the only frame, reset the queu, + // else, move the second frame to the front + if ( member->outVideoFramesTail == member->outVideoFrames ) + { + member->outVideoFrames = NULL ; + member->outVideoFramesTail = NULL ; + } + else + { + // move the pointer to the next frame + member->outVideoFramesTail = member->outVideoFramesTail->prev ; + + // reset it's 'next' pointer + if ( member->outVideoFramesTail != NULL ) + member->outVideoFramesTail->next = NULL ; + } + + // separate the conf frame from the list + cfr->next = NULL ; + cfr->prev = NULL ; + + // decriment frame count + member->outVideoFramesCount-- ; + ast_mutex_unlock(&member->lock); + return cfr ; + } + + ast_mutex_unlock(&member->lock); + return NULL ; +} + + + +int queue_outgoing_video_frame( struct ast_conf_member* member, const struct ast_frame* fr, struct timeval delivery ) +{ + // check on frame + if ( fr == NULL ) + { + ast_log( LOG_ERROR, "unable to queue null frame\n" ) ; + return -1 ; + } + + // check on member + if ( member == NULL ) + { + ast_log( LOG_ERROR, "unable to queue frame for null member\n" ) ; + return -1 ; + } + + ast_mutex_lock(&member->lock); + + // accounting: count the number of outgoing frames for this member + member->video_frames_out++ ; + + // + // we have to drop frames, so we'll drop new frames + // because it's easier ( and doesn't matter much anyway ). + // + if ( member->outVideoFramesCount >= AST_CONF_MAX_VIDEO_QUEUE) + { + ast_log( + AST_CONF_DEBUG, + "unable to queue outgoing VIDEO frame, channel => %s, incoming => %d, outgoing => %d\n", + member->channel_name, member->inVideoFramesCount, member->outVideoFramesCount + ) ; + + // accounting: count dropped outgoing frames + member->video_frames_out_dropped++ ; + ast_mutex_unlock(&member->lock); + return -1 ; + } + + // + // create new conf frame from passed data frame + // + + conf_frame* cfr = create_conf_frame( member, member->outVideoFrames, fr ) ; + + if ( cfr == NULL ) + { + ast_log( LOG_ERROR, "unable to create new conf frame\n" ) ; + + // accounting: count dropped outgoing frames + member->video_frames_out_dropped++ ; + ast_mutex_unlock(&member->lock); + return -1 ; + } + + // set delivery timestamp +#ifdef VIDEO_SETTIMESTAMP + cfr->fr->delivery = delivery ; +#else + cfr->fr->delivery.tv_sec = 0; + cfr->fr->delivery.tv_usec = 0; +#endif + //ast_log (LOG_WARNING,"%d\n",cfr->fr->seqno); + +#ifdef RTP_SEQNO_ZERO + cfr->fr->seqno = 0; +#endif + + if ( member->outVideoFrames == NULL ) + { + // this is the first frame in the buffer + member->outVideoFramesTail = cfr ; + member->outVideoFrames = cfr ; + } + else + { + // put the new frame at the head of the list + member->outVideoFrames = cfr ; + } + + // increment member frame count + member->outVideoFramesCount++ ; + + ast_mutex_unlock(&member->lock); + + // return success + return 0 ; +} + +conf_frame* get_outgoing_dtmf_frame( struct ast_conf_member *member ) +{ + if ( member == NULL ) + { + ast_log( LOG_WARNING, "unable to get frame from null member\n" ) ; + return NULL ; + } + + conf_frame* cfr ; + + // ast_log( AST_CONF_DEBUG, "getting member frames, count => %d\n", member->outFramesCount ) ; + + ast_mutex_lock(&member->lock); + + if ( member->outDTMFFramesCount > AST_CONF_MIN_QUEUE ) + { + cfr = member->outDTMFFramesTail ; + + // if it's the only frame, reset the queu, + // else, move the second frame to the front + if ( member->outDTMFFramesTail == member->outDTMFFrames ) + { + member->outDTMFFrames = NULL ; + member->outDTMFFramesTail = NULL ; + } + else + { + // move the pointer to the next frame + member->outDTMFFramesTail = member->outDTMFFramesTail->prev ; + + // reset it's 'next' pointer + if ( member->outDTMFFramesTail != NULL ) + member->outDTMFFramesTail->next = NULL ; + } + + // separate the conf frame from the list + cfr->next = NULL ; + cfr->prev = NULL ; + + // decriment frame count + member->outDTMFFramesCount-- ; + ast_mutex_unlock(&member->lock); + return cfr ; + } + ast_mutex_unlock(&member->lock); + return NULL ; +} + +conf_frame* get_outgoing_text_frame( struct ast_conf_member *member ) +{ + if ( member == NULL ) + { + ast_log( LOG_WARNING, "unable to get frame from null member\n" ) ; + return NULL ; + } + + conf_frame* cfr ; + + // ast_log( AST_CONF_DEBUG, "getting member frames, count => %d\n", member->outFramesCount ) ; + + ast_mutex_lock(&member->lock); + + if ( member->outTextFramesCount > AST_CONF_MIN_QUEUE ) + { + cfr = member->outTextFramesTail ; + + // if it's the only frame, reset the queu, + // else, move the second frame to the front + if ( member->outTextFramesTail == member->outTextFrames ) + { + member->outTextFrames = NULL ; + member->outTextFramesTail = NULL ; + } + else + { + // move the pointer to the next frame + member->outTextFramesTail = member->outTextFramesTail->prev ; + + // reset it's 'next' pointer + if ( member->outTextFramesTail != NULL ) + member->outTextFramesTail->next = NULL ; + } + + // separate the conf frame from the list + cfr->next = NULL ; + cfr->prev = NULL ; + + // decriment frame count + member->outTextFramesCount-- ; + ast_mutex_unlock(&member->lock); + return cfr ; + } + ast_mutex_unlock(&member->lock); + return NULL ; +} + + +int queue_outgoing_dtmf_frame( struct ast_conf_member* member, const struct ast_frame* fr ) +{ + // check on frame + if ( fr == NULL ) + { + ast_log( LOG_ERROR, "unable to queue null frame\n" ) ; + return -1 ; + } + + // check on member + if ( member == NULL ) + { + ast_log( LOG_ERROR, "unable to queue frame for null member\n" ) ; + return -1 ; + } + + ast_mutex_lock(&member->lock); + + // accounting: count the number of outgoing frames for this member + member->dtmf_frames_out++ ; + + // + // we have to drop frames, so we'll drop new frames + // because it's easier ( and doesn't matter much anyway ). + // + if ( member->outDTMFFramesCount >= AST_CONF_MAX_DTMF_QUEUE) + { + ast_log( + AST_CONF_DEBUG, + "unable to queue outgoing DTMF frame, channel => %s, incoming => %d, outgoing => %d\n", + member->channel_name, member->inDTMFFramesCount, member->outDTMFFramesCount + ) ; + + // accounting: count dropped outgoing frames + member->dtmf_frames_out_dropped++ ; + ast_mutex_unlock(&member->lock); + return -1 ; + } + + // + // create new conf frame from passed data frame + // + + conf_frame* cfr = create_conf_frame( member, member->outDTMFFrames, fr ) ; + + if ( cfr == NULL ) + { + ast_log( LOG_ERROR, "unable to create new conf frame\n" ) ; + + // accounting: count dropped outgoing frames + member->dtmf_frames_out_dropped++ ; + ast_mutex_unlock(&member->lock); + return -1 ; + } + +#ifdef RTP_SEQNO_ZERO + cfr->fr->seqno = 0; +#endif + + if ( member->outDTMFFrames == NULL ) + { + // this is the first frame in the buffer + member->outDTMFFramesTail = cfr ; + member->outDTMFFrames = cfr ; + } + else + { + // put the new frame at the head of the list + member->outDTMFFrames = cfr ; + } + + // increment member frame count + member->outDTMFFramesCount++ ; + + ast_mutex_unlock(&member->lock); + // return success + return 0 ; +} + +int queue_outgoing_text_frame( struct ast_conf_member* member, const struct ast_frame* fr) +{ + // check on frame + if ( fr == NULL ) + { + ast_log( LOG_ERROR, "unable to queue null frame\n" ) ; + return -1 ; + } + + // check on member + if ( member == NULL ) + { + ast_log( LOG_ERROR, "unable to queue frame for null member\n" ) ; + return -1 ; + } + + ast_mutex_lock(&member->lock); + + // accounting: count the number of outgoing frames for this member + member->text_frames_out++ ; + + // + // we have to drop frames, so we'll drop new frames + // because it's easier ( and doesn't matter much anyway ). + // + if ( member->outTextFramesCount >= AST_CONF_MAX_TEXT_QUEUE) + { + ast_log( + AST_CONF_DEBUG, + "unable to queue outgoing text frame, channel => %s, incoming => %d, outgoing => %d\n", + member->channel_name, member->inTextFramesCount, member->outTextFramesCount + ) ; + + // accounting: count dropped outgoing frames + member->text_frames_out_dropped++ ; + ast_mutex_unlock(&member->lock); + return -1 ; + } + + // + // create new conf frame from passed data frame + // + + conf_frame* cfr = create_conf_frame( member, member->outTextFrames, fr ) ; + + if ( cfr == NULL ) + { + ast_log( LOG_ERROR, "unable to create new conf frame\n" ) ; + + // accounting: count dropped outgoing frames + member->text_frames_out_dropped++ ; + ast_mutex_unlock(&member->lock); + return -1 ; + } + +#ifdef RTP_SEQNO_ZERO + cfr->fr->seqno = 0; +#endif + + if ( member->outTextFrames == NULL ) + { + // this is the first frame in the buffer + member->outTextFramesTail = cfr ; + member->outTextFrames = cfr ; + } + else + { + // put the new frame at the head of the list + member->outTextFrames = cfr ; + } + + // increment member frame count + member->outTextFramesCount++ ; + + ast_mutex_unlock(&member->lock); + // return success + return 0 ; +} + + +// +// manager functions +// + +void send_state_change_notifications( struct ast_conf_member* member ) +{ + // ast_log( AST_CONF_DEBUG, "sending state change notification\n" ) ; + + // loop through list of members, sending state changes + while ( member != NULL ) + { + // has the state changed since last time through this loop? + if ( member->speaking_state_notify ) + { + manager_event( + EVENT_FLAG_CALL, + "ConferenceState", + "Channel: %s\r\n" + "State: %s\r\n", + member->channel_name, + ( ( member->speaking_state == 1 ) ? "speaking" : "silent" ) + ) ; + + ast_log( AST_CONF_DEBUG, "member state changed, channel => %s, state => %d, incoming => %d, outgoing => %d\n", + member->channel_name, member->speaking_state, member->inFramesCount, member->outFramesCount ) ; + + member->speaking_state_notify = 0; + } + + // move the pointer to the next member + member = member->next ; + } + + return ; +} + +// +// ast_packer, adapted from ast_smoother +// pack multiple frames together into one packet on the wire. +// + +#define PACKER_SIZE 8000 +#define PACKER_QUEUE 10 // store at most 10 complete packets in the queue + +struct ast_packer { + int framesize; // number of frames per packet on the wire. + int size; + int packet_index; + int format; + int readdata; + int optimizablestream; + int flags; + float samplesperbyte; + struct ast_frame f; + struct timeval delivery; + char data[PACKER_SIZE]; + char framedata[PACKER_SIZE + AST_FRIENDLY_OFFSET]; + int samples; + int sample_queue[PACKER_QUEUE]; + int len_queue[PACKER_QUEUE]; + struct ast_frame *opt; + int len; +}; + +void ast_packer_reset(struct ast_packer *s, int framesize) +{ + memset(s, 0, sizeof(struct ast_packer)); + s->framesize = framesize; + s->packet_index=0; + s->len=0; +} + +struct ast_packer *ast_packer_new(int framesize) +{ + struct ast_packer *s; + if (framesize < 1) + return NULL; + s = malloc(sizeof(struct ast_packer)); + if (s) + ast_packer_reset(s, framesize); + return s; +} + +int ast_packer_get_flags(struct ast_packer *s) +{ + return s->flags; +} + +void ast_packer_set_flags(struct ast_packer *s, int flags) +{ + s->flags = flags; +} + +int ast_packer_feed(struct ast_packer *s, const struct ast_frame *f) +{ + if (f->frametype != AST_FRAME_VOICE) { + ast_log(LOG_WARNING, "Huh? Can't pack a non-voice frame!\n"); + return -1; + } + if (!s->format) { + s->format = f->subclass; + s->samples=0; + } else if (s->format != f->subclass) { + ast_log(LOG_WARNING, "Packer was working on %d format frames, now trying to feed %d?\n", s->format, f->subclass); + return -1; + } + if (s->len + f->datalen > PACKER_SIZE) { + ast_log(LOG_WARNING, "Out of packer space\n"); + return -1; + } + if (s->packet_index >= PACKER_QUEUE ){ + ast_log(LOG_WARNING, "Out of packer queue space\n"); + return -1; + } + + memcpy(s->data + s->len, f->data, f->datalen); + /* If either side is empty, reset the delivery time */ + if (!s->len || (!f->delivery.tv_sec && !f->delivery.tv_usec) || + (!s->delivery.tv_sec && !s->delivery.tv_usec)) + s->delivery = f->delivery; + s->len += f->datalen; +//packer stuff + s->len_queue[s->packet_index] += f->datalen; + s->sample_queue[s->packet_index] += f->samples; + s->samples += f->samples; + + if (s->samples > s->framesize ) + ++s->packet_index; + + return 0; +} + +struct ast_frame *ast_packer_read(struct ast_packer *s) +{ + struct ast_frame *opt; + int len; + /* IF we have an optimization frame, send it */ + if (s->opt) { + opt = s->opt; + s->opt = NULL; + return opt; + } + + /* Make sure we have enough data */ + if (s->samples < s->framesize ){ + return NULL; + } + len = s->len_queue[0]; + if (len > s->len) + len = s->len; + /* Make frame */ + s->f.frametype = AST_FRAME_VOICE; + s->f.subclass = s->format; + s->f.data = s->framedata + AST_FRIENDLY_OFFSET; + s->f.offset = AST_FRIENDLY_OFFSET; + s->f.datalen = len; + s->f.samples = s->sample_queue[0]; + s->f.delivery = s->delivery; + /* Fill Data */ + memcpy(s->f.data, s->data, len); + s->len -= len; + /* Move remaining data to the front if applicable */ + if (s->len) { + /* In principle this should all be fine because if we are sending + G.729 VAD, the next timestamp will take over anyawy */ + memmove(s->data, s->data + len, s->len); + if (s->delivery.tv_sec || s->delivery.tv_usec) { + /* If we have delivery time, increment it, otherwise, leave it at 0 */ + s->delivery.tv_sec += s->sample_queue[0] / 8000.0; + s->delivery.tv_usec += (((int)(s->sample_queue[0])) % 8000) * 125; + if (s->delivery.tv_usec > 1000000) { + s->delivery.tv_usec -= 1000000; + s->delivery.tv_sec += 1; + } + } + } + int j; + s->samples -= s->sample_queue[0]; + if( s->packet_index > 0 ){ + for (j=0; jpacket_index -1 ; j++){ + s->len_queue[j]=s->len_queue[j+1]; + s->sample_queue[j]=s->sample_queue[j+1]; + } + s->len_queue[s->packet_index]=0; + s->sample_queue[s->packet_index]=0; + s->packet_index--; + } else { + s->len_queue[0]=0; + s->sample_queue[0]=0; + } + + + /* Return frame */ + return &s->f; +} + +void ast_packer_free(struct ast_packer *s) +{ + free(s); +} + +int queue_frame_for_listener( + struct ast_conference* conf, + struct ast_conf_member* member, + conf_frame* frame +) +{ + // + // check inputs + // + + if ( conf == NULL ) + { + ast_log( LOG_WARNING, "unable to queue listener frame with null conference\n" ) ; + return -1 ; + } + + if ( member == NULL ) + { + ast_log( LOG_WARNING, "unable to queue listener frame with null member\n" ) ; + return -1 ; + } + + // + // loop over spoken frames looking for member's appropriate match + // + + short found_flag = 0 ; + struct ast_frame* qf ; + + for ( ; frame != NULL ; frame = frame->next ) + { + // we're looking for a null or matching member + if ( frame->member != NULL && frame->member != member ) + continue ; + + if ( frame->fr == NULL ) + { + ast_log( LOG_WARNING, "unknown error queueing frame for listener, frame->fr == NULL\n" ) ; + continue ; + } + + // first, try for a pre-converted frame + qf = frame->converted[ member->write_format_index ] ; + + // convert ( and store ) the frame + if ( qf == NULL ) + { + // make a copy of the slinear version of the frame + qf = ast_frdup( frame->fr ) ; + + if ( qf == NULL ) + { + ast_log( LOG_WARNING, "unable to duplicate frame\n" ) ; + continue ; + } + + // convert using the conference's translation path + qf = convert_frame_from_slinear( conf->from_slinear_paths[ member->write_format_index ], qf ) ; + + // store the converted frame + // ( the frame will be free'd next time through the loop ) + frame->converted[ member->write_format_index ] = qf ; + } + + if ( qf != NULL ) + { + // duplicate the frame before queue'ing it + // ( since this member doesn't own this _shared_ frame ) + // qf = ast_frdup( qf ) ; + + + + if ( queue_outgoing_frame( member, qf, conf->delivery_time ) != 0 ) + { + // free the new frame if it couldn't be queue'd + // XXX NEILS - WOULD BE FREED IN CLEANUPast_frfree( qf ) ; + //qf = NULL ; + } + } + else + { + ast_log( LOG_WARNING, "unable to translate outgoing listener frame, channel => %s\n", member->channel_name ) ; + } + + // set found flag + found_flag = 1 ; + + // break from for loop + break ; + } + + // queue a silent frame + if ( found_flag == 0 ) + queue_silent_frame( conf, member ) ; + + return 0 ; +} + + +int queue_frame_for_speaker( + struct ast_conference* conf, + struct ast_conf_member* member, + conf_frame* frame +) +{ + // + // check inputs + // + + if ( conf == NULL ) + { + ast_log( LOG_WARNING, "unable to queue speaker frame with null conference\n" ) ; + return -1 ; + } + + if ( member == NULL ) + { + ast_log( LOG_WARNING, "unable to queue speaker frame with null member\n" ) ; + return -1 ; + } + + // + // loop over spoken frames looking for member's appropriate match + // + + short found_flag = 0 ; + struct ast_frame* qf ; + + for ( ; frame != NULL ; frame = frame->next ) + { + if ( frame->member != member ) + { + continue ; + } + + if ( frame->fr == NULL ) + { + ast_log( LOG_WARNING, "unable to queue speaker frame with null data\n" ) ; + continue ; + } + + // + // convert and queue frame + // + + // short-cut pointer to the ast_frame + qf = frame->fr ; + + if ( qf->subclass == member->write_format ) + { + // frame is already in correct format, so just queue it + + queue_outgoing_frame( member, qf, conf->delivery_time ) ; + } + else + { + // + // convert frame to member's write format + // ( calling ast_frdup() to make sure the translator's copy sticks around ) + // + qf = convert_frame_from_slinear( member->from_slinear, ast_frdup( qf ) ) ; + + if ( qf != NULL ) + { + // queue frame + queue_outgoing_frame( member, qf, conf->delivery_time ) ; + + // free frame ( the translator's copy ) + ast_frfree( qf ) ; + } + else + { + ast_log( LOG_WARNING, "unable to translate outgoing speaker frame, channel => %s\n", member->channel_name ) ; + } + } + + // set found flag + found_flag = 1 ; + + // we found the frame, skip to the next member + break ; + } + + // queue a silent frame + if ( found_flag == 0 ) + queue_silent_frame( conf, member ) ; + + return 0 ; +} + + +int queue_silent_frame( + struct ast_conference* conf, + struct ast_conf_member* member +) +{ + int c; +#ifdef APP_CONFERENCE_DEBUG + // + // check inputs + // + + if ( conf == NULL ) + { + ast_log( AST_CONF_DEBUG, "unable to queue silent frame for null conference\n" ) ; + return -1 ; + } + + if ( member == NULL ) + { + ast_log( AST_CONF_DEBUG, "unable to queue silent frame for null member\n" ) ; + return -1 ; + } +#endif // APP_CONFERENCE_DEBUG + + // + // initialize static variables + // + + static conf_frame* silent_frame = NULL ; + static struct ast_frame* qf = NULL ; + + if ( silent_frame == NULL ) + { + if ( ( silent_frame = get_silent_frame() ) == NULL ) + { + ast_log( LOG_WARNING, "unable to initialize static silent frame\n" ) ; + return -1 ; + } + } + + + // get the appropriate silent frame + qf = silent_frame->converted[ member->write_format_index ] ; + + if ( qf == NULL ) + { + // + // we need to do this to avoid echo on the speaker's line. + // translators seem to be single-purpose, i.e. they + // can't be used simultaneously for multiple audio streams + // + + struct ast_trans_pvt* trans = ast_translator_build_path( member->write_format, AST_FORMAT_SLINEAR ) ; + + if ( trans != NULL ) + { + // attempt ( five times ) to get a silent frame + // to make sure we provice the translator with enough data + for ( c = 0 ; c < 5 ; ++c ) + { + // translate the frame + qf = ast_translate( trans, silent_frame->fr, 0 ) ; + + // break if we get a frame + if ( qf != NULL ) break ; + } + + if ( qf != NULL ) + { + // isolate the frame so we can keep it around after trans is free'd + qf = ast_frisolate( qf ) ; + + // cache the new, isolated frame + silent_frame->converted[ member->write_format_index ] = qf ; + } + + ast_translator_free_path( trans ) ; + } + } + + // + // queue the frame, if it's not null, + // otherwise there was an error + // + if ( qf != NULL ) + { + queue_outgoing_frame( member, qf, conf->delivery_time ) ; + } + else + { + ast_log( LOG_ERROR, "unable to translate outgoing silent frame, channel => %s\n", member->channel_name ) ; + } + + return 0 ; +} + + + +void member_process_outgoing_frames(struct ast_conference* conf, + struct ast_conf_member *member, + struct conf_frame *send_frames) +{ + ast_mutex_lock(&member->lock); + + // skip members that are not ready + if ( member->ready_for_outgoing == 0 ) + { + ast_mutex_unlock(&member->lock); + return ; + } + + // skip no receive audio clients + if ( member->norecv_audio ) + { + ast_mutex_unlock(&member->lock); + return; + } + + if ( member->local_speaking_state == 0 ) + { + // queue listener frame + queue_frame_for_listener( conf, member, send_frames ) ; + } + else + { + // queue speaker frame + queue_frame_for_speaker( conf, member, send_frames ) ; + } + ast_mutex_unlock(&member->lock); +} + +// Functions that will increase and decrease speaker_count in a secure way, locking the member mutex if required +// Will also set speaking_state flag. +// Returns the previous speaking state +int increment_speaker_count(struct ast_conf_member *member, int lock) +{ + int old_state; + + if ( lock ) + ast_mutex_lock(&member->lock); + + old_state = member->speaking_state; + member->speaker_count++; + member->speaking_state = 1; + + ast_log(AST_CONF_DEBUG, "Increment speaker count: id=%d, count=%d\n", member->id, member->speaker_count); + + // If this is a state change, update the timestamp + if ( old_state == 0 ) + { + member->speaking_state_notify = 1; + member->last_state_change = ast_tvnow(); + } + + if ( lock ) + ast_mutex_unlock(&member->lock); + + return old_state; +} + +int decrement_speaker_count(struct ast_conf_member *member, int lock) +{ + int old_state; + + if ( lock ) + ast_mutex_lock(&member->lock); + + old_state = member->speaking_state; + if ( member->speaker_count > 0 ) + member->speaker_count--; + if ( member->speaker_count == 0 ) + member->speaking_state = 0; + + ast_log(AST_CONF_DEBUG, "Decrement speaker count: id=%d, count=%d\n", member->id, member->speaker_count); + + // If this is a state change, update the timestamp + if ( old_state == 1 && member->speaking_state == 0 ) + { + member->speaking_state_notify = 1; + member->last_state_change = ast_tvnow(); + } + + if ( lock ) + ast_mutex_unlock(&member->lock); + + return old_state; +} + +void member_process_spoken_frames(struct ast_conference* conf, + struct ast_conf_member *member, + struct conf_frame **spoken_frames, + long time_diff, + int *listener_count, + int *speaker_count + ) +{ + struct conf_frame *cfr; + + // acquire member mutex + TIMELOG(ast_mutex_lock( &member->lock ),1,"conf thread member lock") ; + + // check for dead members + if ( member->remove_flag == 1 ) + { + // If this member is the default video source for the conference, then change the default to -1 + if ( member->id == conf->default_video_source_id ) + conf->default_video_source_id = -1; + + if (conf->debug_flag) + { + ast_log( LOG_NOTICE, "found member slated for removal, channel => %s\n", member->channel_name ) ; + } + remove_member( member, conf ) ; + return; + } + + // tell member the number of frames we're going to need ( used to help dropping algorithm ) + member->inFramesNeeded = ( time_diff / AST_CONF_FRAME_INTERVAL ) - 1 ; + + // !!! TESTING !!! + if ( + conf->debug_flag == 1 + && member->inFramesNeeded > 0 + ) + { + ast_log( AST_CONF_DEBUG, "channel => %s, inFramesNeeded => %d, inFramesCount => %d\n", + member->channel_name, member->inFramesNeeded, member->inFramesCount ) ; + } + + // non-listener member should have frames, + // unless silence detection dropped them + cfr = get_incoming_frame( member ) ; + + // handle retrieved frames + if ( cfr == NULL || cfr->fr == NULL ) + { + // Decrement speaker count for us and for driven members + // This happens only for the first missed frame, since we want to + // decrement only on state transitions + if ( member->local_speaking_state == 1 ) + { + decrement_speaker_count(member, 0); + member->local_speaking_state = 0; + // If we're driving another member, decrement its speaker count as well + if ( member->driven_member != NULL ) + decrement_speaker_count(member->driven_member, 1); + } + + // count the listeners + (*listener_count)++ ; + } + else + { + // append the frame to the list of spoken frames + if ( *spoken_frames != NULL ) + { + // add new frame to end of list + cfr->next = *spoken_frames ; + (*spoken_frames)->prev = cfr ; + } + + // point the list at the new frame + *spoken_frames = cfr ; + + // Increment speaker count for us and for driven members + // This happens only on the first received frame, since we want to + // increment only on state transitions + if ( member->local_speaking_state == 0 ) + { + increment_speaker_count(member, 0); + member->local_speaking_state = 1; + + // If we're driving another member, increment its speaker count as well + if ( member->driven_member != NULL ) + increment_speaker_count(member->driven_member, 1); + } + + // count the speakers + (*speaker_count)++ ; + } + + // release member mutex + ast_mutex_unlock( &member->lock ) ; + + return; +} diff --git a/apps/conference/member.h b/apps/conference/member.h new file mode 100644 index 0000000..f4cae3f --- /dev/null +++ b/apps/conference/member.h @@ -0,0 +1,315 @@ + +// $Id: member.h 872 2007-03-05 23:43:10Z sbalea $ + +/* + * app_conference + * + * A channel independent conference application for Asterisk + * + * Copyright (C) 2002, 2003 Junghanns.NET GmbH + * Copyright (C) 2003, 2004 HorizonLive.com, Inc. + * Copyright (C) 2005, 2006 HorizonWimba, Inc. + * Copyright (C) 2007 Wimba, Inc. + * + * Klaus-Peter Junghanns + * + * Video Conferencing support added by + * Neil Stratford + * Copyright (C) 2005, 2005 Vipadia Limited + * + * VAD driven video conferencing, text message support + * and miscellaneous enhancements added by + * Mihai Balea + * + * This program may be modified and distributed under the + * terms of the GNU General Public License. You should have received + * a copy of the GNU General Public License along with this + * program; if not, write to the Free Software Foundation, Inc. + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef _APP_CONF_MEMBER_H +#define _APP_CONF_MEMBER_H + +// +// includes +// + +#include "app_conference.h" +#include "common.h" + +// +// struct declarations +// + +struct ast_conf_soundq +{ + char name[256]; + struct ast_filestream *stream; // the stream + int muted; // should incoming audio be muted while we play? + struct ast_conf_soundq *next; +}; + +struct ast_conf_member +{ + ast_mutex_t lock ; // member data mutex + + struct ast_channel* chan ; // member's channel + char* channel_name ; // member's channel name + + // values passed to create_member () via *data + int priority ; // highest priority gets the channel + char* flags ; // raw member-type flags + char type ; // L = ListenOnly, M = Moderator, S = Standard (Listen/Talk) + char* conf_name ; // name of the conference that own this member + + char *callerid; + char *callername; + + // voice flags + int vad_flag; + int denoise_flag; + int agc_flag; + int via_telephone; + + // video conference params + int id; + int initial_id; + int req_id; + + // muting options - this member will not be heard/seen + int mute_audio; + int mute_video; + + // this member will not hear/see + int norecv_audio; + int norecv_video; + + // this member does not have a camera + int no_camera; + + // is this person a moderator? + int ismoderator; + + // determine by flags and channel name + char connection_type ; // T = telephone, X = iaxclient, S = sip + + // vad voice probability thresholds + float vad_prob_start ; + float vad_prob_continue ; + + // ready flag + short ready_for_outgoing ; + + // input frame queue + conf_frame* inFrames ; + conf_frame* inFramesTail ; + unsigned int inFramesCount ; + conf_frame* inVideoFrames ; + conf_frame* inVideoFramesTail ; + unsigned int inVideoFramesCount ; + conf_frame* inDTMFFrames ; + conf_frame* inDTMFFramesTail ; + unsigned int inDTMFFramesCount ; + conf_frame* inTextFrames ; + conf_frame* inTextFramesTail ; + unsigned int inTextFramesCount ; + + + // input/output smoother + struct ast_smoother *inSmoother; + struct ast_packer *outPacker; + int smooth_size_in; + int smooth_size_out; + int smooth_multiple; + + // frames needed by conference_exec + unsigned int inFramesNeeded ; + unsigned int inVideoFramesNeeded ; + + // used when caching last frame + conf_frame* inFramesLast ; + unsigned int inFramesRepeatLast ; + unsigned short okayToCacheLast ; + + // LL output frame queue + conf_frame* outFrames ; + conf_frame* outFramesTail ; + unsigned int outFramesCount ; + conf_frame* outVideoFrames ; + conf_frame* outVideoFramesTail ; + unsigned int outVideoFramesCount ; + conf_frame* outDTMFFrames ; + conf_frame* outDTMFFramesTail ; + unsigned int outDTMFFramesCount ; + conf_frame* outTextFrames ; + conf_frame* outTextFramesTail ; + unsigned int outTextFramesCount ; + + // LL video switched flag + short conference; + + // switch video by VAD? + short vad_switch; + // switch by dtmf? + short dtmf_switch; + // relay dtmf to manager? + short dtmf_relay; + // initial nat delay flag + short first_frame_received; + // does text messages? + short does_text; + + + // time we last dropped a frame + struct timeval last_in_dropped ; + struct timeval last_out_dropped ; + + // ( not currently used ) + // int samplesperframe ; + + // used for determining need to mix frames + // and for management interface notification + // and for VAD based video switching + short speaking_state_notify ; + short speaking_state ; // This flag will be true if this member or any of its drivers is speaking + short local_speaking_state; // This flag will be true only if this member is speaking + struct timeval last_state_change; + int speaker_count; // Number of drivers (including this member) that are speaking + + // pointer to next member in single-linked list + struct ast_conf_member* next ; + + // accounting values + unsigned long frames_in ; + unsigned long frames_in_dropped ; + unsigned long frames_out ; + unsigned long frames_out_dropped ; + + unsigned long video_frames_in ; + unsigned long video_frames_in_dropped ; + unsigned long video_frames_out ; + unsigned long video_frames_out_dropped ; + + unsigned long dtmf_frames_in ; + unsigned long dtmf_frames_in_dropped ; + unsigned long dtmf_frames_out ; + unsigned long dtmf_frames_out_dropped ; + + unsigned long text_frames_in ; + unsigned long text_frames_in_dropped ; + unsigned long text_frames_out ; + unsigned long text_frames_out_dropped ; + + // for counting sequentially dropped frames + unsigned int sequential_drops ; + unsigned long since_dropped ; + + // start time + struct timeval time_entered ; + struct timeval lastsent_timeval ; + + // flag indicating we should remove this member + short remove_flag ; + short kick_flag ; + +#if ( SILDET == 2 ) + // pointer to speex preprocessor dsp + SpeexPreprocessState *dsp ; + // number of frames to ignore speex_preprocess() + int ignore_speex_count; +#else + // placeholder when preprocessing is not enabled + void* dsp ; +#endif + + // audio format this member is using + int write_format ; + int read_format ; + + int write_format_index ; + int read_format_index ; + + // member frame translators + struct ast_trans_pvt* to_slinear ; + struct ast_trans_pvt* from_slinear ; + + // For playing sounds + struct ast_conf_soundq *soundq; + struct ast_conf_soundq *videoq; + + // Enter/leave sounds + char * enter_snd; + char * leave_snd; + + // Pointer to another member that will be driven from this member's audio + struct ast_conf_member *driven_member; +} ; + +struct conf_member +{ + struct ast_conf_member* realmember ; + struct conf_member* next ; +} ; + +// +// function declarations +// + +int member_exec( struct ast_channel* chan, void* data ) ; + +struct ast_conf_member* check_active_video( int id, struct ast_conference *conf ); + +struct ast_conf_member* create_member( struct ast_channel* chan, const char* data ) ; +struct ast_conf_member* delete_member( struct ast_conf_member* member ) ; + +// incoming queue +int queue_incoming_frame( struct ast_conf_member* member, struct ast_frame* fr ) ; +int queue_incoming_video_frame( struct ast_conf_member* member, const struct ast_frame* fr ) ; +int queue_incoming_dtmf_frame( struct ast_conf_member* member, const struct ast_frame* fr ) ; +conf_frame* get_incoming_frame( struct ast_conf_member* member ) ; +conf_frame* get_incoming_video_frame( struct ast_conf_member* member ) ; +conf_frame* get_incoming_dtmf_frame( struct ast_conf_member* member ) ; + +// outgoing queue +int queue_outgoing_frame( struct ast_conf_member* member, const struct ast_frame* fr, struct timeval delivery ) ; +int __queue_outgoing_frame( struct ast_conf_member* member, const struct ast_frame* fr, struct timeval delivery ) ; +conf_frame* get_outgoing_frame( struct ast_conf_member* member ) ; + +int queue_outgoing_video_frame( struct ast_conf_member* member, const struct ast_frame* fr, struct timeval delivery ) ; +conf_frame* get_outgoing_video_frame( struct ast_conf_member* member ) ; +int queue_outgoing_dtmf_frame( struct ast_conf_member* member, const struct ast_frame* fr ) ; +int queue_outgoing_text_frame( struct ast_conf_member* member, const struct ast_frame* fr ) ; +conf_frame* get_outgoing_dtmf_frame( struct ast_conf_member* member ) ; +conf_frame* get_outgoing_text_frame( struct ast_conf_member* member ) ; + +void send_state_change_notifications( struct ast_conf_member* member ) ; + +int increment_speaker_count(struct ast_conf_member *member, int lock); +int decrement_speaker_count(struct ast_conf_member *member, int lock); + +void member_process_spoken_frames(struct ast_conference* conf, + struct ast_conf_member *member, + struct conf_frame **spoken_frames, + long time_diff, + int *listener_count, + int *speaker_count); + +void member_process_outgoing_frames(struct ast_conference* conf, + struct ast_conf_member *member, + struct conf_frame *send_frames); + +// +// packer functions +// + +struct ast_packer; + +extern struct ast_packer *ast_packer_new(int bytes); +extern void ast_packer_set_flags(struct ast_packer *packer, int flags); +extern int ast_packer_get_flags(struct ast_packer *packer); +extern void ast_packer_free(struct ast_packer *s); +extern void ast_packer_reset(struct ast_packer *s, int bytes); +extern int ast_packer_feed(struct ast_packer *s, const struct ast_frame *f); +extern struct ast_frame *ast_packer_read(struct ast_packer *s); +#endif -- 1.5.3.4